diff --git a/v2/cmd/wails/internal/commands/build/build.go b/v2/cmd/wails/internal/commands/build/build.go
index fbadf6ff6..a55eea565 100644
--- a/v2/cmd/wails/internal/commands/build/build.go
+++ b/v2/cmd/wails/internal/commands/build/build.go
@@ -77,9 +77,9 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
outputFilename := ""
command.StringFlag("o", "Output filename", &outputFilename)
- // Clean build directory
- cleanBuildDirectory := false
- command.BoolFlag("clean", "Clean the build directory before building", &cleanBuildDirectory)
+ // Clean bin directory
+ cleanBinDirectory := false
+ command.BoolFlag("clean", "Clean the bin directory before building", &cleanBinDirectory)
webview2 := "download"
command.StringFlag("webview2", "WebView2 installer strategy: download,embed,browser,error.", &webview2)
@@ -190,28 +190,29 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
// Create BuildOptions
buildOptions := &build.Options{
- Logger: logger,
- OutputType: outputType,
- OutputFile: outputFilename,
- CleanBuildDirectory: cleanBuildDirectory,
- Mode: mode,
- Pack: !noPackage,
- LDFlags: ldflags,
- Compiler: compilerCommand,
- SkipModTidy: skipModTidy,
- Verbosity: verbosity,
- ForceBuild: forceBuild,
- IgnoreFrontend: skipFrontend,
- Compress: compress,
- CompressFlags: compressFlags,
- UserTags: userTags,
- WebView2Strategy: wv2rtstrategy,
- TrimPath: trimpath,
- RaceDetector: raceDetector,
- WindowsConsole: windowsConsole,
- Obfuscated: obfuscated,
- GarbleArgs: garbleargs,
- SkipBindings: skipBindings,
+ Logger: logger,
+ OutputType: outputType,
+ OutputFile: outputFilename,
+ CleanBinDirectory: cleanBinDirectory,
+ Mode: mode,
+ Pack: !noPackage,
+ LDFlags: ldflags,
+ Compiler: compilerCommand,
+ SkipModTidy: skipModTidy,
+ Verbosity: verbosity,
+ ForceBuild: forceBuild,
+ IgnoreFrontend: skipFrontend,
+ Compress: compress,
+ CompressFlags: compressFlags,
+ UserTags: userTags,
+ WebView2Strategy: wv2rtstrategy,
+ TrimPath: trimpath,
+ RaceDetector: raceDetector,
+ WindowsConsole: windowsConsole,
+ Obfuscated: obfuscated,
+ GarbleArgs: garbleargs,
+ SkipBindings: skipBindings,
+ ProjectData: projectOptions,
}
// Start a new tabwriter
@@ -225,6 +226,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
_, _ = fmt.Fprintf(w, "Compiler: \t%s\n", compilerPath)
_, _ = fmt.Fprintf(w, "Skip Bindings: \t%t\n", skipBindings)
_, _ = fmt.Fprintf(w, "Build Mode: \t%s\n", modeString)
+ _, _ = fmt.Fprintf(w, "Frontend Directory: \t%s\n", projectOptions.GetFrontendDir())
_, _ = fmt.Fprintf(w, "Obfuscated: \t%t\n", buildOptions.Obfuscated)
if buildOptions.Obfuscated {
_, _ = fmt.Fprintf(w, "Garble Args: \t%s\n", buildOptions.GarbleArgs)
@@ -232,7 +234,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
_, _ = fmt.Fprintf(w, "Skip Frontend: \t%t\n", skipFrontend)
_, _ = fmt.Fprintf(w, "Compress: \t%t\n", buildOptions.Compress)
_, _ = fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
- _, _ = fmt.Fprintf(w, "Clean Build Dir: \t%t\n", buildOptions.CleanBuildDirectory)
+ _, _ = fmt.Fprintf(w, "Clean Bin Dir: \t%t\n", buildOptions.CleanBinDirectory)
_, _ = fmt.Fprintf(w, "LDFlags: \t\"%s\"\n", buildOptions.LDFlags)
_, _ = fmt.Fprintf(w, "Tags: \t[%s]\n", strings.Join(buildOptions.UserTags, ","))
_, _ = fmt.Fprintf(w, "Race Detector: \t%t\n", buildOptions.RaceDetector)
@@ -358,7 +360,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
}
buildOptions.IgnoreFrontend = true
- buildOptions.CleanBuildDirectory = false
+ buildOptions.CleanBinDirectory = false
// Output stats
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.\n", compiledBinary, time.Since(start).Round(time.Millisecond).String()))
diff --git a/v2/cmd/wails/internal/commands/build/gomod.go b/v2/cmd/wails/internal/commands/build/gomod.go
index 19eefd7c6..29bc9ffb6 100644
--- a/v2/cmd/wails/internal/commands/build/gomod.go
+++ b/v2/cmd/wails/internal/commands/build/gomod.go
@@ -1,13 +1,13 @@
package build
import (
- "os"
- "path/filepath"
-
+ "fmt"
"github.com/wailsapp/wails/v2/cmd/wails/internal"
+ "github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/gomod"
"github.com/wailsapp/wails/v2/internal/goversion"
"github.com/wailsapp/wails/v2/pkg/clilogger"
+ "os"
)
func SyncGoMod(logger *clilogger.CLILogger, updateWailsVersion bool) error {
@@ -15,7 +15,10 @@ func SyncGoMod(logger *clilogger.CLILogger, updateWailsVersion bool) error {
if err != nil {
return err
}
- gomodFilename := filepath.Join(cwd, "go.mod")
+ gomodFilename := fs.FindFileInParents(cwd, "go.mod")
+ if gomodFilename == "" {
+ return fmt.Errorf("no go.mod file found")
+ }
gomodData, err := os.ReadFile(gomodFilename)
if err != nil {
return err
diff --git a/v2/cmd/wails/internal/commands/dev/dev.go b/v2/cmd/wails/internal/commands/dev/dev.go
index d61bc1c7c..f9d9a30fa 100644
--- a/v2/cmd/wails/internal/commands/dev/dev.go
+++ b/v2/cmd/wails/internal/commands/dev/dev.go
@@ -166,6 +166,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
}
buildOptions := generateBuildOptions(flags)
+ buildOptions.ProjectData = projectConfig
buildOptions.SkipBindings = flags.skipBindings
buildOptions.Logger = logger
@@ -214,7 +215,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// frontend:dev:watcher command.
frontendDevAutoDiscovery := projectConfig.IsFrontendDevServerURLAutoDiscovery()
if command := projectConfig.DevWatcherCommand; command != "" {
- closer, devServerURL, err := runFrontendDevWatcherCommand(cwd, command, frontendDevAutoDiscovery)
+ closer, devServerURL, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery)
if err != nil {
return err
}
@@ -358,8 +359,8 @@ func generateBuildOptions(flags devFlags) *build.Options {
// loadAndMergeProjectConfig reconciles flags passed to the CLI with project config settings and updates
// the project config if necessary
-func loadAndMergeProjectConfig(cwd string, flags *devFlags) (*project.Project, error) {
- projectConfig, err := project.Load(cwd)
+func loadAndMergeProjectConfig(projectFileDirectory string, flags *devFlags) (*project.Project, error) {
+ projectConfig, err := project.Load(projectFileDirectory)
if err != nil {
return nil, err
}
@@ -396,11 +397,11 @@ func loadAndMergeProjectConfig(cwd string, flags *devFlags) (*project.Project, e
}
if flags.wailsjsdir == "" && projectConfig.WailsJSDir != "" {
- flags.wailsjsdir = projectConfig.WailsJSDir
+ flags.wailsjsdir = projectConfig.GetWailsJSDir()
}
if flags.wailsjsdir == "" {
- flags.wailsjsdir = "./frontend"
+ flags.wailsjsdir = projectConfig.GetFrontendDir()
}
if flags.wailsjsdir != projectConfig.WailsJSDir {
@@ -433,15 +434,14 @@ func loadAndMergeProjectConfig(cwd string, flags *devFlags) (*project.Project, e
}
// runFrontendDevWatcherCommand will run the `frontend:dev:watcher` command if it was given, ex- `npm run dev`
-func runFrontendDevWatcherCommand(cwd string, devCommand string, discoverViteServerURL bool) (func(), string, error) {
+func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool) (func(), string, error) {
ctx, cancel := context.WithCancel(context.Background())
scanner := NewStdoutScanner()
- dir := filepath.Join(cwd, "frontend")
cmdSlice := strings.Split(devCommand, " ")
cmd := exec.CommandContext(ctx, cmdSlice[0], cmdSlice[1:]...)
cmd.Stderr = os.Stderr
cmd.Stdout = scanner
- cmd.Dir = dir
+ cmd.Dir = frontendDirectory
setParentGID(cmd)
if err := cmd.Start(); err != nil {
diff --git a/v2/examples/customlayout/.gitignore b/v2/examples/customlayout/.gitignore
new file mode 100644
index 000000000..53e9ed8b5
--- /dev/null
+++ b/v2/examples/customlayout/.gitignore
@@ -0,0 +1,3 @@
+build/bin
+node_modules
+myfrontend/wailsjs
\ No newline at end of file
diff --git a/v2/examples/customlayout/README.md b/v2/examples/customlayout/README.md
new file mode 100644
index 000000000..e4d79d4ec
--- /dev/null
+++ b/v2/examples/customlayout/README.md
@@ -0,0 +1,4 @@
+# README
+
+This is an example project that shows how to use a custom layout.
+Run `wails build` in the `cmd/customlayout` directory to build the project.
\ No newline at end of file
diff --git a/v2/examples/customlayout/build/README.md b/v2/examples/customlayout/build/README.md
new file mode 100644
index 000000000..3018a06c4
--- /dev/null
+++ b/v2/examples/customlayout/build/README.md
@@ -0,0 +1,35 @@
+# Build Directory
+
+The build directory is used to house all the build files and assets for your application.
+
+The structure is:
+
+* bin - Output directory
+* darwin - macOS specific files
+* windows - Windows specific files
+
+## Mac
+
+The `darwin` directory holds files specific to Mac builds.
+These may be customised and used as part of the build. To return these files to the default state, simply delete them
+and
+build with `wails build`.
+
+The directory contains the following files:
+
+- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`.
+- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`.
+
+## Windows
+
+The `windows` directory contains the manifest and rc files used when building with `wails build`.
+These may be customised for your application. To return these files to the default state, simply delete them and
+build with `wails build`.
+
+- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to
+ use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file
+ will be created using the `appicon.png` file in the build directory.
+- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`.
+- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer,
+ as well as the application itself (right click the exe -> properties -> details)
+- `wails.exe.manifest` - The main application manifest file.
\ No newline at end of file
diff --git a/v2/examples/customlayout/build/appicon.png b/v2/examples/customlayout/build/appicon.png
new file mode 100644
index 000000000..63617fe4f
Binary files /dev/null and b/v2/examples/customlayout/build/appicon.png differ
diff --git a/v2/examples/customlayout/build/darwin/Info.dev.plist b/v2/examples/customlayout/build/darwin/Info.dev.plist
new file mode 100644
index 000000000..02e7358ee
--- /dev/null
+++ b/v2/examples/customlayout/build/darwin/Info.dev.plist
@@ -0,0 +1,32 @@
+
+
+
+ CFBundlePackageType
+ APPL
+ CFBundleName
+ {{.Info.ProductName}}
+ CFBundleExecutable
+ {{.Name}}
+ CFBundleIdentifier
+ com.wails.{{.Name}}
+ CFBundleVersion
+ {{.Info.ProductVersion}}
+ CFBundleGetInfoString
+ {{.Info.Comments}}
+ CFBundleShortVersionString
+ {{.Info.ProductVersion}}
+ CFBundleIconFile
+ iconfile
+ LSMinimumSystemVersion
+ 10.13.0
+ NSHighResolutionCapable
+ true
+ NSHumanReadableCopyright
+ {{.Info.Copyright}}
+ NSAppTransportSecurity
+
+ NSAllowsLocalNetworking
+
+
+
+
\ No newline at end of file
diff --git a/v2/examples/customlayout/build/darwin/Info.plist b/v2/examples/customlayout/build/darwin/Info.plist
new file mode 100644
index 000000000..e7819a7e8
--- /dev/null
+++ b/v2/examples/customlayout/build/darwin/Info.plist
@@ -0,0 +1,27 @@
+
+
+
+ CFBundlePackageType
+ APPL
+ CFBundleName
+ {{.Info.ProductName}}
+ CFBundleExecutable
+ {{.Name}}
+ CFBundleIdentifier
+ com.wails.{{.Name}}
+ CFBundleVersion
+ {{.Info.ProductVersion}}
+ CFBundleGetInfoString
+ {{.Info.Comments}}
+ CFBundleShortVersionString
+ {{.Info.ProductVersion}}
+ CFBundleIconFile
+ iconfile
+ LSMinimumSystemVersion
+ 10.13.0
+ NSHighResolutionCapable
+ true
+ NSHumanReadableCopyright
+ {{.Info.Copyright}}
+
+
\ No newline at end of file
diff --git a/v2/examples/customlayout/build/windows/icon.ico b/v2/examples/customlayout/build/windows/icon.ico
new file mode 100644
index 000000000..f33479841
Binary files /dev/null and b/v2/examples/customlayout/build/windows/icon.ico differ
diff --git a/v2/examples/customlayout/build/windows/info.json b/v2/examples/customlayout/build/windows/info.json
new file mode 100644
index 000000000..c23c173c9
--- /dev/null
+++ b/v2/examples/customlayout/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/examples/customlayout/build/windows/installer/project.nsi b/v2/examples/customlayout/build/windows/installer/project.nsi
new file mode 100644
index 000000000..3b1588e0c
--- /dev/null
+++ b/v2/examples/customlayout/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/examples/customlayout/build/windows/installer/wails_tools.nsh b/v2/examples/customlayout/build/windows/installer/wails_tools.nsh
new file mode 100644
index 000000000..66dc209a3
--- /dev/null
+++ b/v2/examples/customlayout/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/examples/customlayout/build/windows/wails.exe.manifest b/v2/examples/customlayout/build/windows/wails.exe.manifest
new file mode 100644
index 000000000..17e1a2387
--- /dev/null
+++ b/v2/examples/customlayout/build/windows/wails.exe.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+ true/pm
+ permonitorv2,permonitor
+
+
+
\ No newline at end of file
diff --git a/v2/examples/customlayout/cmd/customlayout/app.go b/v2/examples/customlayout/cmd/customlayout/app.go
new file mode 100644
index 000000000..af53038a1
--- /dev/null
+++ b/v2/examples/customlayout/cmd/customlayout/app.go
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "context"
+ "fmt"
+)
+
+// App struct
+type App struct {
+ ctx context.Context
+}
+
+// NewApp creates a new App application struct
+func NewApp() *App {
+ return &App{}
+}
+
+// startup is called when the app starts. The context is saved
+// so we can call the runtime methods
+func (a *App) startup(ctx context.Context) {
+ a.ctx = ctx
+}
+
+// Greet returns a greeting for the given name
+func (a *App) Greet(name string) string {
+ return fmt.Sprintf("Hello %s, It's show time!", name)
+}
diff --git a/v2/examples/customlayout/cmd/customlayout/main.go b/v2/examples/customlayout/cmd/customlayout/main.go
new file mode 100644
index 000000000..dcb59a80c
--- /dev/null
+++ b/v2/examples/customlayout/cmd/customlayout/main.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "changeme/myfrontend"
+ "github.com/wailsapp/wails/v2"
+ "github.com/wailsapp/wails/v2/pkg/options"
+)
+
+func main() {
+ // Create an instance of the app structure
+ app := NewApp()
+
+ // Create application with options
+ err := wails.Run(&options.App{
+ Title: "customlayout",
+ Width: 1024,
+ Height: 768,
+ Assets: myfrontend.Assets,
+ BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
+ OnStartup: app.startup,
+ Bind: []interface{}{
+ app,
+ },
+ })
+
+ if err != nil {
+ println("Error:", err.Error())
+ }
+}
diff --git a/v2/examples/customlayout/cmd/customlayout/wails.json b/v2/examples/customlayout/cmd/customlayout/wails.json
new file mode 100644
index 000000000..e37c2ec7d
--- /dev/null
+++ b/v2/examples/customlayout/cmd/customlayout/wails.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://wails.io/schemas/config.v2.json",
+ "name": "customlayout",
+ "outputfilename": "customlayout",
+ "build:dir": "../../build",
+ "frontend:dir": "../../myfrontend",
+ "frontend:install": "npm install",
+ "frontend:build": "npm run build",
+ "frontend:dev:watcher": "npm run dev",
+ "frontend:dev:serverUrl": "auto",
+ "author": {
+ "name": "Lea Anthony",
+ "email": "lea.anthony@gmail.com"
+ }
+}
diff --git a/v2/examples/customlayout/go.mod b/v2/examples/customlayout/go.mod
new file mode 100644
index 000000000..97defaeb1
--- /dev/null
+++ b/v2/examples/customlayout/go.mod
@@ -0,0 +1,34 @@
+module changeme
+
+go 1.18
+
+require github.com/wailsapp/wails/v2 v2.1.0
+
+require (
+ github.com/bep/debounce v1.2.1 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
+ github.com/google/uuid v1.1.2 // indirect
+ github.com/imdario/mergo v0.3.12 // indirect
+ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
+ github.com/labstack/echo/v4 v4.9.0 // indirect
+ github.com/labstack/gommon v0.3.1 // indirect
+ github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
+ github.com/leaanthony/gosod v1.0.3 // indirect
+ github.com/leaanthony/slicer v1.5.0 // indirect
+ github.com/mattn/go-colorable v0.1.11 // indirect
+ github.com/mattn/go-isatty v0.0.14 // indirect
+ github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/samber/lo v1.27.1 // indirect
+ github.com/tkrajina/go-reflector v0.5.5 // indirect
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ github.com/valyala/fasttemplate v1.2.1 // indirect
+ github.com/wailsapp/mimetype v1.4.1 // indirect
+ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
+ golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
+ golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
+ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
+ golang.org/x/text v0.3.7 // indirect
+)
+
+replace github.com/wailsapp/wails/v2 v2.1.0 => ../..
diff --git a/v2/examples/customlayout/go.sum b/v2/examples/customlayout/go.sum
new file mode 100644
index 000000000..13bb6bbe1
--- /dev/null
+++ b/v2/examples/customlayout/go.sum
@@ -0,0 +1,79 @@
+github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
+github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
+github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
+github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=
+github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
+github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
+github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
+github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
+github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
+github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
+github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
+github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
+github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
+github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
+github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
+github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
+github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
+github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc=
+github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/samber/lo v1.27.1 h1:sTXwkRiIFIQG+G0HeAvOEnGjqWeWtI9cg5/n51KrxPg=
+github.com/samber/lo v1.27.1/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
+github.com/tkrajina/go-reflector v0.5.5 h1:gwoQFNye30Kk7NrExj8zm3zFtrGPqOkzFMLuQZg1DtQ=
+github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
+github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
+github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
+golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
+golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/v2/examples/customlayout/myfrontend/assets.go b/v2/examples/customlayout/myfrontend/assets.go
new file mode 100644
index 000000000..a6dec2f8f
--- /dev/null
+++ b/v2/examples/customlayout/myfrontend/assets.go
@@ -0,0 +1,6 @@
+package myfrontend
+
+import "embed"
+
+//go:embed all:dist
+var Assets embed.FS
diff --git a/v2/examples/customlayout/myfrontend/index.html b/v2/examples/customlayout/myfrontend/index.html
new file mode 100644
index 000000000..1ceda7392
--- /dev/null
+++ b/v2/examples/customlayout/myfrontend/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ customlayout
+
+
+
+
+
+
diff --git a/v2/examples/customlayout/myfrontend/package.json b/v2/examples/customlayout/myfrontend/package.json
new file mode 100644
index 000000000..4ac881798
--- /dev/null
+++ b/v2/examples/customlayout/myfrontend/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "frontend",
+ "private": true,
+ "version": "0.0.0",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "vite": "^2.9.9"
+ }
+}
\ No newline at end of file
diff --git a/v2/examples/customlayout/myfrontend/src/app.css b/v2/examples/customlayout/myfrontend/src/app.css
new file mode 100644
index 000000000..59d06f692
--- /dev/null
+++ b/v2/examples/customlayout/myfrontend/src/app.css
@@ -0,0 +1,54 @@
+#logo {
+ display: block;
+ width: 50%;
+ height: 50%;
+ margin: auto;
+ padding: 10% 0 0;
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 100% 100%;
+ background-origin: content-box;
+}
+
+.result {
+ height: 20px;
+ line-height: 20px;
+ margin: 1.5rem auto;
+}
+
+.input-box .btn {
+ width: 60px;
+ height: 30px;
+ line-height: 30px;
+ border-radius: 3px;
+ border: none;
+ margin: 0 0 0 20px;
+ padding: 0 8px;
+ cursor: pointer;
+}
+
+.input-box .btn:hover {
+ background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);
+ color: #333333;
+}
+
+.input-box .input {
+ border: none;
+ border-radius: 3px;
+ outline: none;
+ height: 30px;
+ line-height: 30px;
+ padding: 0 10px;
+ background-color: rgba(240, 240, 240, 1);
+ -webkit-font-smoothing: antialiased;
+}
+
+.input-box .input:hover {
+ border: none;
+ background-color: rgba(255, 255, 255, 1);
+}
+
+.input-box .input:focus {
+ border: none;
+ background-color: rgba(255, 255, 255, 1);
+}
\ No newline at end of file
diff --git a/v2/examples/customlayout/myfrontend/src/assets/fonts/OFL.txt b/v2/examples/customlayout/myfrontend/src/assets/fonts/OFL.txt
new file mode 100644
index 000000000..9cac04ce8
--- /dev/null
+++ b/v2/examples/customlayout/myfrontend/src/assets/fonts/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com),
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/v2/examples/customlayout/myfrontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/v2/examples/customlayout/myfrontend/src/assets/fonts/nunito-v16-latin-regular.woff2
new file mode 100644
index 000000000..2f9cc5964
Binary files /dev/null and b/v2/examples/customlayout/myfrontend/src/assets/fonts/nunito-v16-latin-regular.woff2 differ
diff --git a/v2/examples/customlayout/myfrontend/src/assets/images/logo-universal.png b/v2/examples/customlayout/myfrontend/src/assets/images/logo-universal.png
new file mode 100644
index 000000000..d63303bfa
Binary files /dev/null and b/v2/examples/customlayout/myfrontend/src/assets/images/logo-universal.png differ
diff --git a/v2/examples/customlayout/myfrontend/src/main.js b/v2/examples/customlayout/myfrontend/src/main.js
new file mode 100644
index 000000000..4ad5a2cae
--- /dev/null
+++ b/v2/examples/customlayout/myfrontend/src/main.js
@@ -0,0 +1,43 @@
+import './style.css';
+import './app.css';
+
+import logo from './assets/images/logo-universal.png';
+import {Greet} from '../wailsjs/go/main/App';
+
+document.querySelector('#app').innerHTML = `
+
+
Please enter your name below 👇
+
+
+
+
+
+`;
+document.getElementById('logo').src = logo;
+
+let nameElement = document.getElementById("name");
+nameElement.focus();
+let resultElement = document.getElementById("result");
+
+// Setup the greet function
+window.greet = function () {
+ // Get name
+ let name = nameElement.value;
+
+ // Check if the input is empty
+ if (name === "") return;
+
+ // Call App.Greet(name)
+ try {
+ Greet(name)
+ .then((result) => {
+ // Update result with data back from App.Greet()
+ resultElement.innerText = result;
+ })
+ .catch((err) => {
+ console.error(err);
+ });
+ } catch (err) {
+ console.error(err);
+ }
+};
diff --git a/v2/examples/customlayout/myfrontend/src/style.css b/v2/examples/customlayout/myfrontend/src/style.css
new file mode 100644
index 000000000..3940d6c63
--- /dev/null
+++ b/v2/examples/customlayout/myfrontend/src/style.css
@@ -0,0 +1,26 @@
+html {
+ background-color: rgba(27, 38, 54, 1);
+ text-align: center;
+ color: white;
+}
+
+body {
+ margin: 0;
+ color: white;
+ font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
+ "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
+ sans-serif;
+}
+
+@font-face {
+ font-family: "Nunito";
+ font-style: normal;
+ font-weight: 400;
+ src: local(""),
+ url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2");
+}
+
+#app {
+ height: 100vh;
+ text-align: center;
+}
diff --git a/v2/internal/app/app_bindings.go b/v2/internal/app/app_bindings.go
index b87f41cd8..14438126c 100644
--- a/v2/internal/app/app_bindings.go
+++ b/v2/internal/app/app_bindings.go
@@ -60,29 +60,27 @@ func generateBindings(bindings *binding.Bindings) error {
return err
}
- if projectConfig.WailsJSDir == "" {
- projectConfig.WailsJSDir = filepath.Join(cwd, "frontend")
- }
- wrapperDir := filepath.Join(projectConfig.WailsJSDir, "wailsjs", "runtime")
- _ = os.RemoveAll(wrapperDir)
+ wailsjsbasedir := filepath.Join(projectConfig.GetWailsJSDir(), "wailsjs")
+
+ runtimeDir := filepath.Join(wailsjsbasedir, "runtime")
+ _ = os.RemoveAll(runtimeDir)
extractor := gosod.New(wrapper.RuntimeWrapper)
- err = extractor.Extract(wrapperDir, nil)
+ err = extractor.Extract(runtimeDir, nil)
if err != nil {
return err
}
- targetDir := filepath.Join(projectConfig.WailsJSDir, "wailsjs", "go")
- err = os.RemoveAll(targetDir)
+ goBindingsDir := filepath.Join(wailsjsbasedir, "go")
+ err = os.RemoveAll(goBindingsDir)
if err != nil {
return err
}
- _ = fs.MkDirs(targetDir)
+ _ = fs.MkDirs(goBindingsDir)
- err = bindings.GenerateGoBindings(targetDir)
+ err = bindings.GenerateGoBindings(goBindingsDir)
if err != nil {
return err
}
- wailsJSDir := filepath.Join(projectConfig.WailsJSDir, "wailsjs")
- return fs.SetPermissions(wailsJSDir, 0755)
+ return fs.SetPermissions(wailsjsbasedir, 0755)
}
diff --git a/v2/internal/fs/fs.go b/v2/internal/fs/fs.go
index 84e42a392..7deae9810 100644
--- a/v2/internal/fs/fs.go
+++ b/v2/internal/fs/fs.go
@@ -378,3 +378,27 @@ func FindPathToFile(fsys fs.FS, file string) (string, error) {
}
return "", fmt.Errorf("no index.html found")
}
+
+// FindFileInParents searches for a file in the current directory and all parent directories.
+// Returns the absolute path to the file if found, otherwise an empty string
+func FindFileInParents(path string, filename string) string {
+
+ // Check for bad paths
+ if _, err := os.Stat(path); err != nil {
+ return ""
+ }
+
+ var pathToFile string
+ for {
+ pathToFile = filepath.Join(path, filename)
+ if _, err := os.Stat(pathToFile); err == nil {
+ break
+ }
+ parent := filepath.Dir(path)
+ if parent == path {
+ return ""
+ }
+ path = parent
+ }
+ return pathToFile
+}
diff --git a/v2/internal/fs/fs_test.go b/v2/internal/fs/fs_test.go
index 2cc524123..efc4929e6 100644
--- a/v2/internal/fs/fs_test.go
+++ b/v2/internal/fs/fs_test.go
@@ -1,6 +1,7 @@
package fs
import (
+ "github.com/samber/lo"
"os"
"path/filepath"
"testing"
@@ -10,22 +11,80 @@ import (
func TestRelativePath(t *testing.T) {
- is := is.New(t)
+ i := is.New(t)
cwd, err := os.Getwd()
- is.Equal(err, nil)
+ i.Equal(err, nil)
// Check current directory
actual := RelativePath(".")
- is.Equal(actual, cwd)
+ i.Equal(actual, cwd)
// Check 2 parameters
actual = RelativePath("..", "fs")
- is.Equal(actual, cwd)
+ i.Equal(actual, cwd)
// Check 3 parameters including filename
actual = RelativePath("..", "fs", "fs.go")
expected := filepath.Join(cwd, "fs.go")
- is.Equal(actual, expected)
+ i.Equal(actual, expected)
}
+
+func Test_FindFileInParents(t *testing.T) {
+ tests := []struct {
+ name string
+ setup func() (startDir string, configDir string)
+ wantErr bool
+ }{
+ {
+ name: "should error when no wails.json file is found in local or parent dirs",
+ setup: func() (string, string) {
+ tempDir := os.TempDir()
+ testDir := lo.Must(os.MkdirTemp(tempDir, "projectPath"))
+ _ = os.MkdirAll(testDir, 0755)
+ return testDir, ""
+ },
+ wantErr: true,
+ },
+ {
+ name: "should find wails.json in local path",
+ setup: func() (string, string) {
+ tempDir := os.TempDir()
+ testDir := lo.Must(os.MkdirTemp(tempDir, "projectPath"))
+ _ = os.MkdirAll(testDir, 0755)
+ configFile := filepath.Join(testDir, "wails.json")
+ _ = os.WriteFile(configFile, []byte("{}"), 0755)
+ return testDir, configFile
+ },
+ wantErr: false,
+ },
+ {
+ name: "should find wails.json in parent path",
+ setup: func() (string, string) {
+ tempDir := os.TempDir()
+ testDir := lo.Must(os.MkdirTemp(tempDir, "projectPath"))
+ _ = os.MkdirAll(testDir, 0755)
+ parentDir := filepath.Dir(testDir)
+ configFile := filepath.Join(parentDir, "wails.json")
+ _ = os.WriteFile(configFile, []byte("{}"), 0755)
+ return testDir, configFile
+ },
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ path, expectedPath := tt.setup()
+ defer func() {
+ if expectedPath != "" {
+ _ = os.Remove(expectedPath)
+ }
+ }()
+ got := FindFileInParents(path, "wails.json")
+ if got != expectedPath {
+ t.Errorf("FindFileInParents() got = %v, want %v", got, expectedPath)
+ }
+ })
+ }
+}
diff --git a/v2/internal/project/project.go b/v2/internal/project/project.go
index 0e3c8f32a..7e2e7ffd5 100644
--- a/v2/internal/project/project.go
+++ b/v2/internal/project/project.go
@@ -2,6 +2,7 @@ package project
import (
"encoding/json"
+ "github.com/samber/lo"
"os"
"path/filepath"
"runtime"
@@ -39,7 +40,7 @@ type Project struct {
Path string
// Build directory
- BuildDir string
+ BuildDir string `json:"build:dir"`
// The output filename
OutputFilename string `json:"outputfilename"`
@@ -88,6 +89,30 @@ type Project struct {
// Garble
Obfuscated bool `json:"obfuscated"`
GarbleArgs string `json:"garbleargs"`
+
+ // Frontend directory
+ FrontendDir string `json:"frontend:dir"`
+}
+
+func (p *Project) GetFrontendDir() string {
+ if filepath.IsAbs(p.FrontendDir) {
+ return p.FrontendDir
+ }
+ return filepath.Join(p.Path, p.FrontendDir)
+}
+
+func (p *Project) GetWailsJSDir() string {
+ if filepath.IsAbs(p.WailsJSDir) {
+ return p.WailsJSDir
+ }
+ return filepath.Join(p.Path, p.WailsJSDir)
+}
+
+func (p *Project) GetBuildDir() string {
+ if filepath.IsAbs(p.BuildDir) {
+ return p.BuildDir
+ }
+ return filepath.Join(p.Path, p.BuildDir)
}
func (p *Project) GetDevBuildCommand() string {
@@ -119,6 +144,70 @@ func (p *Project) Save() error {
return os.WriteFile(p.filename, data, 0755)
}
+func (p *Project) setDefaults() {
+
+ if p.Path == "" {
+ p.Path = lo.Must(os.Getwd())
+ }
+ if p.Version == "" {
+ p.Version = "2"
+ }
+ // Create default name if not given
+ if p.Name == "" {
+ p.Name = "wailsapp"
+ }
+ if p.OutputFilename == "" {
+ p.OutputFilename = p.Name
+ }
+ if p.FrontendDir == "" {
+ p.FrontendDir = "frontend"
+ }
+ if p.WailsJSDir == "" {
+ p.WailsJSDir = p.FrontendDir
+ }
+ if p.BuildDir == "" {
+ p.BuildDir = "build"
+ }
+ if p.DebounceMS == 0 {
+ p.DebounceMS = 100
+ }
+ if p.DevServer == "" {
+ p.DevServer = "localhost:34115"
+ }
+ if p.NSISType == "" {
+ p.NSISType = "multiple"
+ }
+ if p.Info.CompanyName == "" {
+ p.Info.CompanyName = p.Name
+ }
+ if p.Info.ProductName == "" {
+ p.Info.ProductName = p.Name
+ }
+ if p.Info.ProductVersion == "" {
+ p.Info.ProductVersion = "1.0.0"
+ }
+ if p.Info.Copyright == nil {
+ v := "Copyright........."
+ p.Info.Copyright = &v
+ }
+ if p.Info.Comments == nil {
+ v := "Built using Wails (https://wails.io)"
+ p.Info.Comments = &v
+ }
+
+ // Fix up OutputFilename
+ switch runtime.GOOS {
+ case "windows":
+ if !strings.HasSuffix(p.OutputFilename, ".exe") {
+ p.OutputFilename += ".exe"
+ }
+ case "darwin", "linux":
+ if strings.HasSuffix(p.OutputFilename, ".exe") {
+ p.OutputFilename = strings.TrimSuffix(p.OutputFilename, ".exe")
+ }
+ }
+}
+
// Author stores details about the application author
type Author struct {
Name string `json:"name"`
@@ -133,69 +222,28 @@ type Info struct {
Comments *string `json:"comments"`
}
+// Parse the given JSON data into a Project struct
+func Parse(projectData []byte) (*Project, error) {
+ project := &Project{}
+ err := json.Unmarshal(projectData, project)
+ if err != nil {
+ return nil, err
+ }
+ project.setDefaults()
+ return project, nil
+}
+
// Load the project from the current working directory
func Load(projectPath string) (*Project, error) {
-
- // Attempt to load project.json
projectFile := filepath.Join(projectPath, "wails.json")
rawBytes, err := os.ReadFile(projectFile)
if err != nil {
return nil, err
}
-
- // Unmarshal JSON
- var result Project
- err = json.Unmarshal(rawBytes, &result)
+ result, err := Parse(rawBytes)
if err != nil {
return nil, err
}
-
- // Fix up our project paths
result.filename = projectFile
-
- if result.Version == "" {
- result.Version = "2"
- }
-
- // Create default name if not given
- if result.Name == "" {
- result.Name = "wailsapp"
- }
-
- // Fix up OutputFilename
- switch runtime.GOOS {
- case "windows":
- if !strings.HasSuffix(result.OutputFilename, ".exe") {
- result.OutputFilename += ".exe"
- }
- case "darwin", "linux":
- if strings.HasSuffix(result.OutputFilename, ".exe") {
- result.OutputFilename = strings.TrimSuffix(result.OutputFilename, ".exe")
- }
- }
-
- 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.io)"
- result.Info.Comments = &v
- }
-
- if result.DevServer == "" {
- result.DevServer = "localhost:34115"
- }
-
- // Return our project data
- return &result, nil
+ return result, nil
}
diff --git a/v2/internal/project/project_test.go b/v2/internal/project/project_test.go
new file mode 100644
index 000000000..0ad29d960
--- /dev/null
+++ b/v2/internal/project/project_test.go
@@ -0,0 +1,129 @@
+package project_test
+
+import (
+ "github.com/samber/lo"
+ "github.com/wailsapp/wails/v2/internal/project"
+ "os"
+ "path/filepath"
+ "runtime"
+ "testing"
+)
+
+func TestProject_GetFrontendDir(t *testing.T) {
+ cwd := lo.Must(os.Getwd())
+ tests := []struct {
+ name string
+ inputJSON string
+ want string
+ wantError bool
+ }{
+ {
+ name: "Should use 'frontend' by default",
+ inputJSON: "{}",
+ want: filepath.ToSlash(filepath.Join(cwd, "frontend")),
+ wantError: false,
+ },
+ {
+ name: "Should resolve a relative path with no project path",
+ inputJSON: `{"frontend:dir": "./frontend"}`,
+ want: filepath.ToSlash(filepath.Join(cwd, "frontend")),
+ wantError: false,
+ },
+ {
+ name: "Should resolve a relative path with project path set",
+ inputJSON: `{"frontend:dir": "./frontend", "projectdir": "/home/user/project"}`,
+ want: "/home/user/project/frontend",
+ wantError: false,
+ },
+ {
+ name: "Should honour an absolute path",
+ inputJSON: func() string {
+ if runtime.GOOS == "windows" {
+ return `{"frontend:dir": "C:\\frontend", "projectdir": "C:\\project"}`
+ } else {
+ return `{"frontend:dir": "/home/myproject/frontend", "projectdir": "/home/user/project"}`
+ }
+ }(),
+ want: func() string {
+ if runtime.GOOS == "windows" {
+ return `C:/frontend`
+ } else {
+ return `/home/myproject/frontend`
+ }
+ }(),
+ wantError: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ proj, err := project.Parse([]byte(tt.inputJSON))
+ if err != nil && !tt.wantError {
+ t.Errorf("Error parsing project: %s", err)
+ }
+ got := proj.GetFrontendDir()
+ got = filepath.ToSlash(got)
+ if got != tt.want {
+ t.Errorf("GetFrontendDir() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+func TestProject_GetBuildDir(t *testing.T) {
+ cwd := lo.Must(os.Getwd())
+ tests := []struct {
+ name string
+ inputJSON string
+ want string
+ wantError bool
+ }{
+ {
+ name: "Should use 'build' by default",
+ inputJSON: "{}",
+ want: filepath.ToSlash(filepath.Join(cwd, "build")),
+ wantError: false,
+ },
+ {
+ name: "Should resolve a relative path with no project path",
+ inputJSON: `{"build:dir": "./build"}`,
+ want: filepath.ToSlash(filepath.Join(cwd, "build")),
+ wantError: false,
+ },
+ {
+ name: "Should resolve a relative path with project path set",
+ inputJSON: `{"build:dir": "./build", "projectdir": "/home/user/project"}`,
+ want: "/home/user/project/build",
+ wantError: false,
+ },
+ {
+ name: "Should honour an absolute path",
+ inputJSON: func() string {
+ if runtime.GOOS == "windows" {
+ return `{"build:dir": "C:\\build", "projectdir": "C:\\project"}`
+ } else {
+ return `{"build:dir": "/home/myproject/build", "projectdir": "/home/user/project"}`
+ }
+ }(),
+ want: func() string {
+ if runtime.GOOS == "windows" {
+ return `C:/build`
+ } else {
+ return `/home/myproject/build`
+ }
+ }(),
+ wantError: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ proj, err := project.Parse([]byte(tt.inputJSON))
+ if err != nil && !tt.wantError {
+ t.Errorf("Error parsing project: %s", err)
+ }
+ got := proj.GetBuildDir()
+ got = filepath.ToSlash(got)
+ if got != tt.want {
+ t.Errorf("GetFrontendDir() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/v2/pkg/buildassets/buildassets.go b/v2/pkg/buildassets/buildassets.go
index 5cd128793..26401745d 100644
--- a/v2/pkg/buildassets/buildassets.go
+++ b/v2/pkg/buildassets/buildassets.go
@@ -7,11 +7,11 @@ import (
"fmt"
iofs "io/fs"
"os"
- "path"
"path/filepath"
"text/template"
"github.com/leaanthony/gosod"
+ "github.com/samber/lo"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/project"
)
@@ -19,9 +19,12 @@ import (
//go:embed build
var assets embed.FS
-const (
- rootFolder = "build"
-)
+// Same as assets but chrooted into /build/
+var buildAssets iofs.FS
+
+func init() {
+ buildAssets = lo.Must(iofs.Sub(assets, "build"))
+}
// Install will install all default project assets
func Install(targetDir string) error {
@@ -36,20 +39,19 @@ func Install(targetDir string) error {
// 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)))
+ return filepath.Clean(filepath.Join(projectData.GetBuildDir(), filepath.FromSlash(file)))
}
// 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)
+ localFilePath := GetLocalPath(projectData, file)
- content, err := iofs.ReadFile(fs, file)
+ content, err := os.ReadFile(localFilePath)
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)
+ content, err := iofs.ReadFile(buildAssets, file)
if err != nil {
return nil, err
}
@@ -83,8 +85,7 @@ func ReadFileWithProjectData(projectData *project.Project, file string) ([]byte,
// 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)
+ content, err := iofs.ReadFile(buildAssets, file)
if err != nil {
return nil, fmt.Errorf("Unable to read file %s: %w", file, err)
}
@@ -124,15 +125,15 @@ func resolveProjectData(content []byte, projectData *project.Project) ([]byte, e
}
func writeFileSystemFile(projectData *project.Project, file string, content []byte) error {
- path := filepath.Clean(filepath.Join(projectData.Path, filepath.FromSlash(file)))
+ targetPath := GetLocalPath(projectData, file)
- if dir := filepath.Dir(path); !fs.DirExists(dir) {
+ if dir := filepath.Dir(targetPath); !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 {
+ if err := os.WriteFile(targetPath, content, 0644); err != nil {
return err
}
return nil
diff --git a/v2/pkg/commands/build/base.go b/v2/pkg/commands/build/base.go
index 71bc11d63..4dc46caf7 100644
--- a/v2/pkg/commands/build/base.go
+++ b/v2/pkg/commands/build/base.go
@@ -263,9 +263,9 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
}
// Get application build directory
- appDir := options.BuildDirectory
- if options.CleanBuildDirectory {
- err = cleanBuildDirectory(options)
+ appDir := options.BinDirectory
+ if options.CleanBinDirectory {
+ err = cleanBinDirectory(options)
if err != nil {
return err
}
@@ -291,6 +291,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
cmd.Dir = b.projectData.Path
// Add CGO flags
+ // TODO: Remove this as we don't generate headers any more
// We use the project/build dir as a temporary place for our generated c headers
buildBaseDir, err := fs.RelativeToCwd("build")
if err != nil {
@@ -487,6 +488,9 @@ func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand st
// Shortcut installation
if install == false {
+ if verbose {
+ println("Skipping npm install")
+ }
return nil
}
@@ -543,7 +547,10 @@ func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
verbose := b.options.Verbosity == VERBOSE
- frontendDir := filepath.Join(b.projectData.Path, "frontend")
+ frontendDir := b.projectData.GetFrontendDir()
+ if !fs.DirExists(frontendDir) {
+ return fmt.Errorf("frontend directory '%s' does not exist", frontendDir)
+ }
// Check there is an 'InstallCommand' provided in wails.json
installCommand := b.projectData.InstallCommand
diff --git a/v2/pkg/commands/build/build.go b/v2/pkg/commands/build/build.go
index 2c85565a1..28704f373 100644
--- a/v2/pkg/commands/build/build.go
+++ b/v2/pkg/commands/build/build.go
@@ -35,38 +35,38 @@ const (
// Options contains all the build options as well as the project data
type Options struct {
- LDFlags string // Optional flags to pass to linker
- UserTags []string // Tags to pass to the Go compiler
- Logger *clilogger.CLILogger // All output to the logger
- OutputType string // EG: desktop, server....
- Mode Mode // release or dev
- ProjectData *project.Project // The project data
- Pack bool // Create a package for the app after building
- Platform string // The platform to build for
- Arch string // The architecture to build for
- Compiler string // The compiler command to use
- SkipModTidy bool // Skip mod tidy before compile
- IgnoreFrontend bool // Indicates if the frontend does not need building
- IgnoreApplication bool // Indicates if the application does not need building
- OutputFile string // Override the output filename
- BuildDirectory string // Directory to use for building the application
- CleanBuildDirectory bool // Indicates if the build directory should be cleaned before building
- CompiledBinary string // Fully qualified path to the compiled binary
- KeepAssets bool // Keep the generated assets/files
- Verbosity int // Verbosity level (0 - silent, 1 - default, 2 - verbose)
- Compress bool // Compress the final binary
- CompressFlags string // Flags to pass to UPX
- WebView2Strategy string // WebView2 installer strategy
- RunDelve bool // Indicates if we should run delve after the build
- WailsJSDir string // Directory to generate the wailsjs module
- ForceBuild bool // Force
- BundleName string // Bundlename for Mac
- TrimPath bool // Use Go's trimpath compiler flag
- RaceDetector bool // Build with Go's race detector
- WindowsConsole bool // Indicates that the windows console should be kept
- Obfuscated bool // Indicates that bound methods should be obfuscated
- GarbleArgs string // The arguments for Garble
- SkipBindings bool // Skip binding generation
+ LDFlags string // Optional flags to pass to linker
+ UserTags []string // Tags to pass to the Go compiler
+ Logger *clilogger.CLILogger // All output to the logger
+ OutputType string // EG: desktop, server....
+ Mode Mode // release or dev
+ ProjectData *project.Project // The project data
+ Pack bool // Create a package for the app after building
+ Platform string // The platform to build for
+ Arch string // The architecture to build for
+ Compiler string // The compiler command to use
+ SkipModTidy bool // Skip mod tidy before compile
+ IgnoreFrontend bool // Indicates if the frontend does not need building
+ IgnoreApplication bool // Indicates if the application does not need building
+ OutputFile string // Override the output filename
+ BinDirectory string // Directory to use to write the built applications
+ CleanBinDirectory bool // Indicates if the bin output directory should be cleaned before building
+ CompiledBinary string // Fully qualified path to the compiled binary
+ KeepAssets bool // Keep the generated assets/files
+ Verbosity int // Verbosity level (0 - silent, 1 - default, 2 - verbose)
+ Compress bool // Compress the final binary
+ CompressFlags string // Flags to pass to UPX
+ WebView2Strategy string // WebView2 installer strategy
+ RunDelve bool // Indicates if we should run delve after the build
+ WailsJSDir string // Directory to generate the wailsjs module
+ ForceBuild bool // Force
+ BundleName string // Bundlename for Mac
+ TrimPath bool // Use Go's trimpath compiler flag
+ RaceDetector bool // Build with Go's race detector
+ WindowsConsole bool // Indicates that the windows console should be kept
+ Obfuscated bool // Indicates that bound methods should be obfuscated
+ GarbleArgs string // The arguments for Garble
+ SkipBindings bool // Skip binding generation
}
// Build the project!
@@ -81,48 +81,32 @@ func Build(options *Options) (string, error) {
return "", err
}
- // Load project
- projectData, err := project.Load(cwd)
- if err != nil {
- return "", err
- }
- options.ProjectData = projectData
-
- // Add default path if it doesn't exist
- if projectData.Path == "" {
- projectData.Path = cwd
- }
-
// wails js dir
- if projectData.WailsJSDir != "" {
- options.WailsJSDir = projectData.WailsJSDir
- } else {
- options.WailsJSDir = filepath.Join(cwd, "frontend")
- }
+ options.WailsJSDir = options.ProjectData.GetWailsJSDir()
// Set build directory
- options.BuildDirectory = filepath.Join(options.ProjectData.Path, "build", "bin")
+ options.BinDirectory = filepath.Join(options.ProjectData.GetBuildDir(), "bin")
// Save the project type
- projectData.OutputType = options.OutputType
+ options.ProjectData.OutputType = options.OutputType
// Create builder
var builder Builder
- switch projectData.OutputType {
+ switch options.OutputType {
case "desktop":
builder = newDesktopBuilder(options)
case "dev":
builder = newDesktopBuilder(options)
default:
- return "", fmt.Errorf("cannot build assets for output type %s", projectData.OutputType)
+ return "", fmt.Errorf("cannot build assets for output type %s", options.ProjectData.OutputType)
}
// Set up our clean up method
defer builder.CleanUp()
// Initialise Builder
- builder.SetProjectData(projectData)
+ builder.SetProjectData(options.ProjectData)
hookArgs := map[string]string{
"${platform}": options.Platform + "/" + options.Arch,
@@ -263,9 +247,9 @@ func execBuildApplication(builder Builder, options *Options) (string, error) {
// Build amd64 first
options.Arch = "amd64"
options.OutputFile = amd64Filename
- options.CleanBuildDirectory = false
+ options.CleanBinDirectory = false
if options.Verbosity == VERBOSE {
- outputLogger.Println("\nBuilding AMD64 Target: %s", filepath.Join(options.BuildDirectory, options.OutputFile))
+ outputLogger.Println("\nBuilding AMD64 Target: %s", filepath.Join(options.BinDirectory, options.OutputFile))
}
err := builder.CompileProject(options)
if err != nil {
@@ -274,9 +258,9 @@ func execBuildApplication(builder Builder, options *Options) (string, error) {
// Build arm64
options.Arch = "arm64"
options.OutputFile = arm64Filename
- options.CleanBuildDirectory = false
+ options.CleanBinDirectory = false
if options.Verbosity == VERBOSE {
- outputLogger.Println("Building ARM64 Target: %s", filepath.Join(options.BuildDirectory, options.OutputFile))
+ outputLogger.Println("Building ARM64 Target: %s", filepath.Join(options.BinDirectory, options.OutputFile))
}
err = builder.CompileProject(options)
@@ -287,21 +271,21 @@ func execBuildApplication(builder Builder, options *Options) (string, error) {
if options.Verbosity == VERBOSE {
outputLogger.Println(" Running lipo: lipo -create -output %s %s %s", outputFile, amd64Filename, arm64Filename)
}
- _, stderr, err := shell.RunCommand(options.BuildDirectory, "lipo", "-create", "-output", outputFile, amd64Filename, arm64Filename)
+ _, stderr, err := shell.RunCommand(options.BinDirectory, "lipo", "-create", "-output", outputFile, amd64Filename, arm64Filename)
if err != nil {
return "", fmt.Errorf("%s - %s", err.Error(), stderr)
}
// Remove temp binaries
- err = fs.DeleteFile(filepath.Join(options.BuildDirectory, amd64Filename))
+ err = fs.DeleteFile(filepath.Join(options.BinDirectory, amd64Filename))
if err != nil {
return "", err
}
- err = fs.DeleteFile(filepath.Join(options.BuildDirectory, arm64Filename))
+ err = fs.DeleteFile(filepath.Join(options.BinDirectory, arm64Filename))
if err != nil {
return "", err
}
options.ProjectData.OutputFilename = outputFile
- options.CompiledBinary = filepath.Join(options.BuildDirectory, outputFile)
+ options.CompiledBinary = filepath.Join(options.BinDirectory, outputFile)
} else {
err := builder.CompileProject(options)
if err != nil {
@@ -392,7 +376,7 @@ func executeBuildHook(outputLogger *clilogger.CLILogger, options *Options, hookI
outputLogger.Println("%s", strings.Join(args, " "))
}
- stdout, stderr, err := shell.RunCommand(options.BuildDirectory, args[0], args[1:]...)
+ stdout, stderr, err := shell.RunCommand(options.BinDirectory, args[0], args[1:]...)
if options.Verbosity == VERBOSE {
println(stdout)
}
diff --git a/v2/pkg/commands/build/builder.go b/v2/pkg/commands/build/builder.go
index 725e9871c..4840341c0 100644
--- a/v2/pkg/commands/build/builder.go
+++ b/v2/pkg/commands/build/builder.go
@@ -8,7 +8,6 @@ import (
// Builder defines a builder that can build Wails applications
type Builder interface {
SetProjectData(projectData *project.Project)
- BuildAssets(*Options) error
BuildFrontend(*clilogger.CLILogger) error
CompileProject(*Options) error
OutputFilename(*Options) string
diff --git a/v2/pkg/commands/build/desktop.go b/v2/pkg/commands/build/desktop.go
index 218866bfa..c54eb3035 100644
--- a/v2/pkg/commands/build/desktop.go
+++ b/v2/pkg/commands/build/desktop.go
@@ -1,10 +1,5 @@
package build
-import (
- "github.com/wailsapp/wails/v2/internal/fs"
- "github.com/wailsapp/wails/v2/pkg/buildassets"
-)
-
// DesktopBuilder builds applications for the desktop
type DesktopBuilder struct {
*BaseBuilder
@@ -15,18 +10,3 @@ func newDesktopBuilder(options *Options) *DesktopBuilder {
BaseBuilder: NewBaseBuilder(options),
}
}
-
-// BuildAssets builds the assets for the desktop application
-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)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
diff --git a/v2/pkg/commands/build/packager.go b/v2/pkg/commands/build/packager.go
index 9e2681a20..92ce37e90 100644
--- a/v2/pkg/commands/build/packager.go
+++ b/v2/pkg/commands/build/packager.go
@@ -3,14 +3,12 @@ package build
import (
"bytes"
"fmt"
- "image"
- "os"
- "path/filepath"
- "runtime"
-
"github.com/leaanthony/winicon"
"github.com/tc-hib/winres"
"github.com/tc-hib/winres/version"
+ "image"
+ "os"
+ "path/filepath"
"github.com/jackmordaunt/icns"
"github.com/pkg/errors"
@@ -41,10 +39,10 @@ func packageProject(options *Options, platform string) error {
return nil
}
-// cleanBuildDirectory will remove an existing build directory and recreate it
-func cleanBuildDirectory(options *Options) error {
+// cleanBinDirectory will remove an existing bin directory and recreate it
+func cleanBinDirectory(options *Options) error {
- buildDirectory := options.BuildDirectory
+ buildDirectory := options.BinDirectory
// Clear out old builds
if fs.DirExists(buildDirectory) {
@@ -63,11 +61,6 @@ func cleanBuildDirectory(options *Options) error {
return nil
}
-// Gets the platform dependent package assets directory
-func getPackageAssetsDirectory() string {
- return fs.RelativePath("internal/packager", runtime.GOOS)
-}
-
func packageApplicationForDarwin(options *Options) error {
var err error
@@ -78,7 +71,7 @@ func packageApplicationForDarwin(options *Options) error {
bundlename = options.ProjectData.Name + ".app"
}
- contentsDirectory := filepath.Join(options.BuildDirectory, bundlename, "/Contents")
+ contentsDirectory := filepath.Join(options.BinDirectory, bundlename, "/Contents")
exeDir := filepath.Join(contentsDirectory, "/MacOS")
err = fs.MkDirs(exeDir, 0755)
if err != nil {
@@ -174,30 +167,7 @@ func packageApplicationForWindows(options *Options) error {
return nil
}
-func packageApplicationForLinux(options *Options) error {
- // Generate icon
- //var err error
- //err = generateIcoFile(options)
- //if err != nil {
- // return err
- //}
- //
- //// Ensure Manifest is present
- //err = generateManifest(options)
- //if err != nil {
- // return err
- //}
- //
- //// Create syso file
- //err = compileResources(options)
- //if err != nil {
- // return err
- //}
-
- return nil
-}
-
-func generateManifest(options *Options) error {
+func packageApplicationForLinux(_ *Options) error {
return nil
}
@@ -238,7 +208,7 @@ func compileResources(options *Options) error {
defer func() {
os.Chdir(currentDir)
}()
- windowsDir := filepath.Join(options.ProjectData.Path, "build", "windows")
+ windowsDir := filepath.Join(options.ProjectData.GetBuildDir(), "windows")
err = os.Chdir(windowsDir)
if err != nil {
return err
diff --git a/website/static/schemas/config.v2.json b/website/static/schemas/config.v2.json
index f5a6935a2..0f90bd15b 100644
--- a/website/static/schemas/config.v2.json
+++ b/website/static/schemas/config.v2.json
@@ -19,15 +19,39 @@
"type": "string",
"description": "Additional directories to trigger reloads (comma separated). Often, this is only used for advanced asset configurations."
},
+ "build:dir": {
+ "type": "string",
+ "description": "The directory where the build files reside.",
+ "default": "build",
+ "examples": [
+ "./build",
+ "/absolute/path/to/build",
+ "C:\\absolute\\path\\to\\build"
+ ]
+ },
+ "frontend:dir": {
+ "type": "string",
+ "description": "The directory where the frontend files reside.",
+ "default": "frontend",
+ "examples": [
+ "./src",
+ "/absolute/path/to/frontend",
+ "C:\\absolute\\path\\to\\frontend"
+ ]
+ },
"frontend:install": {
"type": "string",
"description": "The command to install dependencies. Run in the frontend directory.",
- "examples": [ "npm install" ]
+ "examples": [
+ "npm install"
+ ]
},
"frontend:build": {
"type": "string",
"description": "The command to build the assets. Run in the frontend directory.",
- "examples": ["npm run build"]
+ "examples": [
+ "npm run build"
+ ]
},
"frontend:dev": {
"type": "string",
@@ -58,7 +82,7 @@
"type": "string",
"description": "Relative path to the directory where the auto-generated JS modules will be created.",
"format": "uri-reference",
- "default": "./frontend"
+ "default": "The value of frontend:dir"
},
"version": {
"description": "Project config version",