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",