mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 04:40:41 +08:00
297 lines
8.1 KiB
Go
297 lines
8.1 KiB
Go
//go:build dev
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"flag"
|
|
"fmt"
|
|
iofs "io/fs"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
|
|
|
"github.com/wailsapp/wails/v2/internal/binding"
|
|
"github.com/wailsapp/wails/v2/internal/frontend/desktop"
|
|
"github.com/wailsapp/wails/v2/internal/frontend/devserver"
|
|
"github.com/wailsapp/wails/v2/internal/frontend/dispatcher"
|
|
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
|
"github.com/wailsapp/wails/v2/internal/fs"
|
|
"github.com/wailsapp/wails/v2/internal/logger"
|
|
"github.com/wailsapp/wails/v2/internal/menumanager"
|
|
pkglogger "github.com/wailsapp/wails/v2/pkg/logger"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
)
|
|
|
|
func (a *App) Run() error {
|
|
err := a.frontend.Run(a.ctx)
|
|
a.frontend.RunMainLoop()
|
|
a.frontend.WindowClose()
|
|
if a.shutdownCallback != nil {
|
|
a.shutdownCallback(a.ctx)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// CreateApp creates the app!
|
|
func CreateApp(appoptions *options.App) (*App, error) {
|
|
var err error
|
|
|
|
ctx := context.Background()
|
|
ctx = context.WithValue(ctx, "debug", true)
|
|
ctx = context.WithValue(ctx, "devtoolsEnabled", true)
|
|
|
|
// Set up logger if the appoptions.LogLevel is an invalid value, set it to the default log level
|
|
appoptions.LogLevel, err = pkglogger.StringToLogLevel(appoptions.LogLevel.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
myLogger := logger.New(appoptions.Logger)
|
|
myLogger.SetLogLevel(appoptions.LogLevel)
|
|
|
|
// Check for CLI Flags
|
|
devFlags := flag.NewFlagSet("dev", flag.ContinueOnError)
|
|
|
|
var assetdirFlag *string
|
|
var devServerFlag *string
|
|
var frontendDevServerURLFlag *string
|
|
var loglevelFlag *string
|
|
|
|
assetdir := os.Getenv("assetdir")
|
|
if assetdir == "" {
|
|
assetdirFlag = devFlags.String("assetdir", "", "Directory to serve assets")
|
|
}
|
|
|
|
devServer := os.Getenv("devserver")
|
|
if devServer == "" {
|
|
devServerFlag = devFlags.String("devserver", "", "Address to bind the wails dev server to")
|
|
}
|
|
|
|
frontendDevServerURL := os.Getenv("frontenddevserverurl")
|
|
if frontendDevServerURL == "" {
|
|
frontendDevServerURLFlag = devFlags.String("frontenddevserverurl", "", "URL of the external frontend dev server")
|
|
}
|
|
|
|
loglevel := os.Getenv("loglevel")
|
|
if loglevel == "" {
|
|
loglevelFlag = devFlags.String("loglevel", appoptions.LogLevel.String(), "Loglevel to use - Trace, Debug, Info, Warning, Error")
|
|
}
|
|
|
|
// If we weren't given the assetdir in the environment variables
|
|
if assetdir == "" {
|
|
// Parse args but ignore errors in case -appargs was used to pass in args for the app.
|
|
_ = devFlags.Parse(os.Args[1:])
|
|
if assetdirFlag != nil {
|
|
assetdir = *assetdirFlag
|
|
}
|
|
if devServerFlag != nil {
|
|
devServer = *devServerFlag
|
|
}
|
|
if frontendDevServerURLFlag != nil {
|
|
frontendDevServerURL = *frontendDevServerURLFlag
|
|
}
|
|
if loglevelFlag != nil {
|
|
loglevel = *loglevelFlag
|
|
}
|
|
}
|
|
|
|
assetConfig, err := assetserver.BuildAssetServerConfig(appoptions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if assetConfig.Assets == nil && frontendDevServerURL != "" {
|
|
myLogger.Warning("No AssetServer.Assets has been defined but a frontend DevServer, the frontend DevServer will not be used.")
|
|
frontendDevServerURL = ""
|
|
assetdir = ""
|
|
}
|
|
|
|
if frontendDevServerURL != "" {
|
|
_, port, err := net.SplitHostPort(devServer)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to determine port of DevServer: %s", err)
|
|
}
|
|
|
|
ctx = context.WithValue(ctx, "assetserverport", port)
|
|
|
|
ctx = context.WithValue(ctx, "frontenddevserverurl", frontendDevServerURL)
|
|
|
|
externalURL, err := url.Parse(frontendDevServerURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if externalURL.Host == "" {
|
|
return nil, fmt.Errorf("Invalid frontend:dev:serverUrl missing protocol scheme?")
|
|
}
|
|
|
|
waitCb := func() { myLogger.Debug("Waiting for frontend DevServer '%s' to be ready", externalURL) }
|
|
if !checkPortIsOpen(externalURL.Host, time.Minute, waitCb) {
|
|
myLogger.Error("Timeout waiting for frontend DevServer")
|
|
}
|
|
|
|
handler := assetserver.NewExternalAssetsHandler(myLogger, assetConfig, externalURL)
|
|
assetConfig.Assets = nil
|
|
assetConfig.Handler = handler
|
|
assetConfig.Middleware = nil
|
|
|
|
myLogger.Info("Serving assets from frontend DevServer URL: %s", frontendDevServerURL)
|
|
} else {
|
|
if assetdir == "" {
|
|
// If no assetdir has been defined, let's try to infer it from the project root and the asset FS.
|
|
assetdir, err = tryInferAssetDirFromFS(assetConfig.Assets)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to infer the AssetDir from your Assets fs.FS: %w", err)
|
|
}
|
|
}
|
|
|
|
if assetdir != "" {
|
|
// Let's override the assets to serve from on disk, if needed
|
|
absdir, err := filepath.Abs(assetdir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
myLogger.Info("Serving assets from disk: %s", absdir)
|
|
assetConfig.Assets = os.DirFS(absdir)
|
|
|
|
ctx = context.WithValue(ctx, "assetdir", assetdir)
|
|
}
|
|
}
|
|
|
|
// Migrate deprecated options to the new AssetServer option
|
|
appoptions.Assets = nil
|
|
appoptions.AssetsHandler = nil
|
|
appoptions.AssetServer = &assetConfig
|
|
|
|
if devServer != "" {
|
|
ctx = context.WithValue(ctx, "devserver", devServer)
|
|
}
|
|
|
|
if loglevel != "" {
|
|
level, err := pkglogger.StringToLogLevel(loglevel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Only set the log level if it's different from the appoptions.LogLevel
|
|
if level != appoptions.LogLevel {
|
|
myLogger.SetLogLevel(level)
|
|
}
|
|
}
|
|
|
|
// Attach logger to context
|
|
ctx = context.WithValue(ctx, "logger", myLogger)
|
|
ctx = context.WithValue(ctx, "buildtype", "dev")
|
|
|
|
// Preflight checks
|
|
err = PreflightChecks(appoptions, myLogger)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Merge default options
|
|
options.MergeDefaults(appoptions)
|
|
|
|
var menuManager *menumanager.Manager
|
|
|
|
// Process the application menu
|
|
if appoptions.Menu != nil {
|
|
// Create the menu manager
|
|
menuManager = menumanager.NewManager()
|
|
err = menuManager.SetApplicationMenu(appoptions.Menu)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Create binding exemptions - Ugly hack. There must be a better way
|
|
bindingExemptions := []interface{}{
|
|
appoptions.OnStartup,
|
|
appoptions.OnShutdown,
|
|
appoptions.OnDomReady,
|
|
appoptions.OnBeforeClose,
|
|
}
|
|
appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, false, appoptions.EnumBind)
|
|
|
|
eventHandler := runtime.NewEvents(myLogger)
|
|
ctx = context.WithValue(ctx, "events", eventHandler)
|
|
messageDispatcher := dispatcher.NewDispatcher(ctx, myLogger, appBindings, eventHandler, appoptions.ErrorFormatter, appoptions.DisablePanicRecovery)
|
|
|
|
// Create the frontends and register to event handler
|
|
desktopFrontend := desktop.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher)
|
|
appFrontend := devserver.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher, menuManager, desktopFrontend)
|
|
eventHandler.AddFrontend(appFrontend)
|
|
eventHandler.AddFrontend(desktopFrontend)
|
|
|
|
ctx = context.WithValue(ctx, "frontend", appFrontend)
|
|
result := &App{
|
|
ctx: ctx,
|
|
frontend: appFrontend,
|
|
logger: myLogger,
|
|
menuManager: menuManager,
|
|
startupCallback: appoptions.OnStartup,
|
|
shutdownCallback: appoptions.OnShutdown,
|
|
debug: true,
|
|
devtoolsEnabled: true,
|
|
}
|
|
|
|
result.options = appoptions
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
func tryInferAssetDirFromFS(assets iofs.FS) (string, error) {
|
|
if _, isEmbedFs := assets.(embed.FS); !isEmbedFs {
|
|
// We only infer the assetdir for embed.FS assets
|
|
return "", nil
|
|
}
|
|
|
|
path, err := fs.FindPathToFile(assets, "index.html")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
path, err = filepath.Abs(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if _, err := os.Stat(filepath.Join(path, "index.html")); err != nil {
|
|
if os.IsNotExist(err) {
|
|
err = fmt.Errorf(
|
|
"inferred assetdir '%s' does not exist or does not contain an 'index.html' file, "+
|
|
"please specify it with -assetdir or set it in wails.json",
|
|
path)
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
return path, nil
|
|
}
|
|
|
|
func checkPortIsOpen(host string, timeout time.Duration, waitCB func()) (ret bool) {
|
|
if timeout == 0 {
|
|
timeout = time.Minute
|
|
}
|
|
|
|
deadline := time.Now().Add(timeout)
|
|
for time.Now().Before(deadline) {
|
|
conn, _ := net.DialTimeout("tcp", host, 2*time.Second)
|
|
if conn != nil {
|
|
conn.Close()
|
|
return true
|
|
}
|
|
|
|
waitCB()
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
return false
|
|
}
|