From e4b03f510bbad8095feaf3d19eed9060b792537a Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 6 Feb 2021 21:50:21 +1100 Subject: [PATCH] First step to bridge support --- v2/cmd/wails/internal/commands/dev/dev.go | 58 +++--- v2/internal/app/default.go | 2 +- v2/internal/app/dev.go | 239 ++++++++++++++++++++++ v2/internal/bridge/bridge.go | 61 ++++++ v2/internal/process/process.go | 6 +- v2/pkg/commands/build/build.go | 2 + 6 files changed, 332 insertions(+), 36 deletions(-) create mode 100644 v2/internal/app/dev.go create mode 100644 v2/internal/bridge/bridge.go diff --git a/v2/cmd/wails/internal/commands/dev/dev.go b/v2/cmd/wails/internal/commands/dev/dev.go index dabd431d2..6f34334df 100644 --- a/v2/cmd/wails/internal/commands/dev/dev.go +++ b/v2/cmd/wails/internal/commands/dev/dev.go @@ -10,9 +10,10 @@ import ( "syscall" "time" + "github.com/pkg/errors" + "github.com/fsnotify/fsnotify" "github.com/leaanthony/clir" - "github.com/leaanthony/slicer" "github.com/wailsapp/wails/v2/internal/fs" "github.com/wailsapp/wails/v2/internal/process" "github.com/wailsapp/wails/v2/pkg/clilogger" @@ -24,14 +25,6 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error { command := app.NewSubCommand("dev", "Development mode") - outputType := "desktop" - - validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"}) - - // Setup target type flag - description := "Type of application to develop. Valid types: " + validTargetTypes.Join(",") - command.StringFlag("t", description, &outputType) - // Passthrough ldflags ldflags := "" command.StringFlag("ldflags", "optional ldflags", &ldflags) @@ -42,15 +35,10 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error { // extensions to trigger rebuilds extensions := "go" - command.StringFlag("m", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions) + command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions) command.Action(func() error { - // Validate inputs - if !validTargetTypes.Contains(outputType) { - return fmt.Errorf("output type '%s' is not valid", outputType) - } - // Create logger logger := clilogger.New(w) app.PrintBanner() @@ -64,8 +52,9 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error { defer watcher.Close() var debugBinaryProcess *process.Process = nil - var buildFrontend bool = true + var buildFrontend bool = false var extensionsThatTriggerARebuild = strings.Split(extensions, ",") + println(extensionsThatTriggerARebuild) // Setup signal handler quitChannel := make(chan os.Signal, 1) @@ -75,8 +64,10 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error { // Do initial build logger.Println("Building application for development...") - debugBinaryProcess = restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess) - + debugBinaryProcess, err = restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess) + if err != nil { + return err + } go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) { // logger.Println("event: %+v", event) @@ -98,7 +89,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error { // Check for file writes if event.Op&fsnotify.Write == fsnotify.Write { - // logger.Println("modified file: %s", event.Name) + logger.Println("modified file: %s", event.Name) var rebuild bool = false // Iterate all file patterns @@ -119,17 +110,16 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error { return } - if buildFrontend { - logger.Println("Full rebuild triggered: %s updated", event.Name) - } else { - logger.Println("Partial build triggered: %s updated", event.Name) - } + logger.Println("Partial build triggered: %s updated", event.Name) // Do a rebuild // Try and build the app - newBinaryProcess := restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess) - + newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess) + if err != nil { + fmt.Printf("Error during build: %s", err.Error()) + return + } // If we have a new process, save it if newBinaryProcess != nil { debugBinaryProcess = newBinaryProcess @@ -167,7 +157,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error { for quit == false { select { case <-quitChannel: - println() + println("Caught quit") // Notify debouncer to quit debounceQuit <- true quit = true @@ -209,13 +199,13 @@ exit: } } -func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool, debugBinaryProcess *process.Process) *process.Process { +func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process) (*process.Process, error) { - appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand, buildFrontend) + appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand) println() if err != nil { - logger.Println("[ERROR] Build Failed: %s", err.Error()) - return nil + logger.Fatal(err.Error()) + return nil, errors.Wrap(err, "Build Failed:") } logger.Println("Build new binary: %s", appBinary) @@ -244,10 +234,10 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, logger.Fatal("Unable to start application: %s", err.Error()) } - return newProcess + return newProcess, nil } -func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool) (string, error) { +func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) { // Create random output file outputFile := fmt.Sprintf("debug-%d", time.Now().Unix()) @@ -262,7 +252,7 @@ func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, co LDFlags: ldflags, Compiler: compilerCommand, OutputFile: outputFile, - IgnoreFrontend: !buildFrontend, + IgnoreFrontend: true, } return build.Build(buildOptions) diff --git a/v2/internal/app/default.go b/v2/internal/app/default.go index a2a0eb09b..380c251be 100644 --- a/v2/internal/app/default.go +++ b/v2/internal/app/default.go @@ -1,4 +1,4 @@ -// +build !desktop,!hybrid,!server +// +build !desktop,!hybrid,!server,!dev package app diff --git a/v2/internal/app/dev.go b/v2/internal/app/dev.go new file mode 100644 index 000000000..28338e84a --- /dev/null +++ b/v2/internal/app/dev.go @@ -0,0 +1,239 @@ +// +build dev + +package app + +import ( + "context" + "sync" + + "github.com/wailsapp/wails/v2/internal/bridge" + "github.com/wailsapp/wails/v2/internal/menumanager" + + "github.com/wailsapp/wails/v2/pkg/options" + + "github.com/wailsapp/wails/v2/internal/binding" + "github.com/wailsapp/wails/v2/internal/logger" + "github.com/wailsapp/wails/v2/internal/messagedispatcher" + "github.com/wailsapp/wails/v2/internal/runtime" + "github.com/wailsapp/wails/v2/internal/servicebus" + "github.com/wailsapp/wails/v2/internal/signal" + "github.com/wailsapp/wails/v2/internal/subsystem" +) + +// App defines a Wails application structure +type App struct { + appType string + + servicebus *servicebus.ServiceBus + logger *logger.Logger + signal *signal.Manager + options *options.App + + // Subsystems + log *subsystem.Log + runtime *subsystem.Runtime + event *subsystem.Event + //binding *subsystem.Binding + call *subsystem.Call + menu *subsystem.Menu + dispatcher *messagedispatcher.Dispatcher + + menuManager *menumanager.Manager + + // Indicates if the app is in debug mode + debug bool + + // This is our binding DB + bindings *binding.Bindings + + // Application Stores + loglevelStore *runtime.Store + appconfigStore *runtime.Store + + // Startup/Shutdown + startupCallback func(*runtime.Runtime) + shutdownCallback func() + + // Bridge + bridge *bridge.Bridge +} + +// Create App +func CreateApp(appoptions *options.App) (*App, error) { + + // Merge default options + options.MergeDefaults(appoptions) + + // Set up logger + myLogger := logger.New(appoptions.Logger) + myLogger.SetLogLevel(appoptions.LogLevel) + + // Create the menu manager + menuManager := menumanager.NewManager() + + // Process the application menu + menuManager.SetApplicationMenu(options.GetApplicationMenu(appoptions)) + + // Process context menus + contextMenus := options.GetContextMenus(appoptions) + for _, contextMenu := range contextMenus { + menuManager.AddContextMenu(contextMenu) + } + + // Process tray menus + trayMenus := options.GetTrayMenus(appoptions) + for _, trayMenu := range trayMenus { + menuManager.AddTrayMenu(trayMenu) + } + + // Create binding exemptions - Ugly hack. There must be a better way + bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown} + + result := &App{ + appType: "dev", + bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions), + logger: myLogger, + servicebus: servicebus.New(myLogger), + startupCallback: appoptions.Startup, + shutdownCallback: appoptions.Shutdown, + bridge: bridge.NewBridge(myLogger), + } + + result.options = appoptions + + // Initialise the app + err := result.Init() + + return result, err + +} + +// Run the application +func (a *App) Run() error { + + var err error + + // Setup a context + var subsystemWaitGroup sync.WaitGroup + parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup) + ctx, cancel := context.WithCancel(parentContext) + + // Setup signal handler + signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback) + if err != nil { + return err + } + a.signal = signalsubsystem + a.signal.Start() + + // Start the service bus + a.servicebus.Debug() + err = a.servicebus.Start() + if err != nil { + return err + } + + runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback) + if err != nil { + return err + } + a.runtime = runtimesubsystem + err = a.runtime.Start() + if err != nil { + return err + } + + // Application Stores + a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel) + a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options) + + // Start the logging subsystem + log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore) + if err != nil { + return err + } + a.log = log + err = a.log.Start() + if err != nil { + return err + } + + // create the dispatcher + dispatcher, err := messagedispatcher.New(a.servicebus, a.logger) + if err != nil { + return err + } + a.dispatcher = dispatcher + err = dispatcher.Start() + if err != nil { + return err + } + + // Start the eventing subsystem + eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger) + if err != nil { + return err + } + a.event = eventsubsystem + err = a.event.Start() + if err != nil { + return err + } + + // Start the menu subsystem + menusubsystem, err := subsystem.NewMenu(ctx, a.servicebus, a.logger, a.menuManager) + if err != nil { + return err + } + a.menu = menusubsystem + err = a.menu.Start() + if err != nil { + return err + } + + // Start the call subsystem + callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime()) + if err != nil { + return err + } + a.call = callSubsystem + err = a.call.Start() + if err != nil { + return err + } + + // Dump bindings as a debug + bindingDump, err := a.bindings.ToJSON() + if err != nil { + return err + } + + err = a.bridge.Run(dispatcher, bindingDump, a.debug) + a.logger.Trace("Bridge.Run() exited") + if err != nil { + return err + } + + // Close down all the subsystems + a.logger.Trace("Cancelling subsystems") + cancel() + subsystemWaitGroup.Wait() + + a.logger.Trace("Cancelling dispatcher") + dispatcher.Close() + + // Close log + a.logger.Trace("Stopping log") + log.Close() + + a.logger.Trace("Stopping Service bus") + err = a.servicebus.Stop() + if err != nil { + return err + } + + println("Desktop.Run() finished") + + return nil + +} diff --git a/v2/internal/bridge/bridge.go b/v2/internal/bridge/bridge.go new file mode 100644 index 000000000..9b287d785 --- /dev/null +++ b/v2/internal/bridge/bridge.go @@ -0,0 +1,61 @@ +package bridge + +import ( + "log" + "net/http" + + "github.com/gorilla/websocket" + "github.com/wailsapp/wails/v2/internal/logger" + "github.com/wailsapp/wails/v2/internal/messagedispatcher" +) + +type Bridge struct { + upgrader websocket.Upgrader + server *http.Server + myLogger *logger.Logger +} + +func NewBridge(myLogger *logger.Logger) *Bridge { + result := &Bridge{ + myLogger: myLogger, + upgrader: websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}, + } + + result.server = &http.Server{Addr: ":34115"} + http.HandleFunc("/bridge", result.wsBridgeHandler) + return result +} + +func (b *Bridge) Run(dispatcher *messagedispatcher.Dispatcher, bindingDump string, debug bool) error { + + b.myLogger.Info("Bridge mode started.") + + err := b.server.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + return err + } + + return nil +} + +func (b *Bridge) wsBridgeHandler(w http.ResponseWriter, r *http.Request) { + c, err := b.upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("upgrade:", err) + return + } + defer c.Close() + for { + mt, message, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + break + } + log.Printf("recv: %s", message) + err = c.WriteMessage(mt, message) + if err != nil { + log.Println("write:", err) + break + } + } +} diff --git a/v2/internal/process/process.go b/v2/internal/process/process.go index e6a925b56..8c1a99bf2 100644 --- a/v2/internal/process/process.go +++ b/v2/internal/process/process.go @@ -1,6 +1,7 @@ package process import ( + "os" "os/exec" "github.com/wailsapp/wails/v2/pkg/clilogger" @@ -16,11 +17,14 @@ type Process struct { // NewProcess creates a new process struct func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Process { - return &Process{ + result := &Process{ logger: logger, cmd: exec.Command(cmd, args...), exitChannel: make(chan bool, 1), } + result.cmd.Stdout = os.Stdout + result.cmd.Stderr = os.Stderr + return result } // Start the process diff --git a/v2/pkg/commands/build/build.go b/v2/pkg/commands/build/build.go index 4cb50aefd..8ed281311 100644 --- a/v2/pkg/commands/build/build.go +++ b/v2/pkg/commands/build/build.go @@ -86,6 +86,8 @@ func Build(options *Options) (string, error) { builder = newHybridBuilder(options) case "server": builder = newServerBuilder(options) + case "dev": + builder = newDesktopBuilder(options) default: return "", fmt.Errorf("cannot build assets for output type %s", projectData.OutputType) }