173 lines
4.5 KiB
Go
173 lines
4.5 KiB
Go
package platform
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates"
|
|
)
|
|
|
|
const helperFlag = "--wails3kit-update-helper"
|
|
|
|
type Detector func(app updates.AppDescriptor) (updates.InstallRoot, error)
|
|
|
|
type Installer struct {
|
|
detector Detector
|
|
}
|
|
|
|
func New(detector Detector) *Installer {
|
|
return &Installer{detector: detector}
|
|
}
|
|
|
|
func (installer *Installer) DetectInstallRoot(app updates.AppDescriptor) (updates.InstallRoot, error) {
|
|
if installer.detector == nil {
|
|
return updates.InstallRoot{}, errors.New("missing install root detector")
|
|
}
|
|
return installer.detector(app)
|
|
}
|
|
|
|
func (installer *Installer) Stage(ctx context.Context, root updates.InstallRoot, file updates.DownloadedFile, release updates.Release) (updates.StagedArtifact, error) {
|
|
stageDir, bundleManifest, err := extractBundle(ctx, file.Path, release.Artifact.Format)
|
|
if err != nil {
|
|
return updates.StagedArtifact{}, err
|
|
}
|
|
|
|
return updates.StagedArtifact{
|
|
Path: stageDir,
|
|
Root: root,
|
|
Release: release,
|
|
Bundle: bundleManifest,
|
|
}, nil
|
|
}
|
|
|
|
func (installer *Installer) SpawnApplyAndRestart(_ context.Context, req updates.ApplyRequest) error {
|
|
helperDir, err := os.MkdirTemp("", "wails3kit-helper-*")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
helperBinary := filepath.Join(helperDir, filepath.Base(req.App.ExecutablePath))
|
|
if err := copyFile(req.App.ExecutablePath, helperBinary, 0o755); err != nil {
|
|
return err
|
|
}
|
|
|
|
payload := helperRequest{
|
|
InstallRoot: req.Root.Path,
|
|
StagedPath: req.Staged.Path,
|
|
Bundle: req.Staged.Bundle,
|
|
Args: append([]string(nil), req.App.Args...),
|
|
WorkingDirectory: req.App.WorkingDirectory,
|
|
}
|
|
|
|
requestPath := filepath.Join(helperDir, "request.json")
|
|
encoded, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(requestPath, encoded, 0o600); err != nil {
|
|
return err
|
|
}
|
|
|
|
command := exec.Command(helperBinary, helperFlag, requestPath)
|
|
command.Dir = helperDir
|
|
command.Stdout = os.Stdout
|
|
command.Stderr = os.Stderr
|
|
return command.Start()
|
|
}
|
|
|
|
func MaybeRun(args []string) (bool, error) {
|
|
if len(args) < 3 || args[1] != helperFlag {
|
|
return false, nil
|
|
}
|
|
return true, runHelper(args[2])
|
|
}
|
|
|
|
func NewCurrent() (*Installer, error) {
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
return New(detectDarwinInstallRoot), nil
|
|
case "windows":
|
|
return New(detectDirectoryInstallRoot), nil
|
|
case "linux":
|
|
return New(detectDirectoryInstallRoot), nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported platform: %s", runtime.GOOS)
|
|
}
|
|
}
|
|
|
|
type helperRequest struct {
|
|
InstallRoot string `json:"installRoot"`
|
|
StagedPath string `json:"stagedPath"`
|
|
Bundle updates.BundleManifest `json:"bundle"`
|
|
Args []string `json:"args,omitempty"`
|
|
WorkingDirectory string `json:"workingDirectory,omitempty"`
|
|
}
|
|
|
|
func runHelper(requestPath string) error {
|
|
bytes, err := os.ReadFile(requestPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var request helperRequest
|
|
if err := json.Unmarshal(bytes, &request); err != nil {
|
|
return err
|
|
}
|
|
|
|
deadline := time.Now().Add(15 * time.Second)
|
|
var applyErr error
|
|
for time.Now().Before(deadline) {
|
|
applyErr = applyBundle(request)
|
|
if applyErr == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
if applyErr != nil {
|
|
return applyErr
|
|
}
|
|
|
|
relaunch := filepath.Join(request.InstallRoot, filepath.FromSlash(request.Bundle.EntryPoint))
|
|
command := exec.Command(relaunch, request.Args...)
|
|
if request.WorkingDirectory != "" {
|
|
command.Dir = request.WorkingDirectory
|
|
} else {
|
|
command.Dir = request.InstallRoot
|
|
}
|
|
command.Stdout = os.Stdout
|
|
command.Stderr = os.Stderr
|
|
return command.Start()
|
|
}
|
|
|
|
func detectDirectoryInstallRoot(app updates.AppDescriptor) (updates.InstallRoot, error) {
|
|
if app.ExecutablePath == "" {
|
|
return updates.InstallRoot{}, errors.New("missing executable path")
|
|
}
|
|
return updates.InstallRoot{Path: filepath.Dir(app.ExecutablePath)}, nil
|
|
}
|
|
|
|
func detectDarwinInstallRoot(app updates.AppDescriptor) (updates.InstallRoot, error) {
|
|
if app.ExecutablePath == "" {
|
|
return updates.InstallRoot{}, errors.New("missing executable path")
|
|
}
|
|
|
|
current := filepath.Dir(app.ExecutablePath)
|
|
for {
|
|
if filepath.Ext(current) == ".app" {
|
|
return updates.InstallRoot{Path: current}, nil
|
|
}
|
|
parent := filepath.Dir(current)
|
|
if parent == current {
|
|
return updates.InstallRoot{}, fmt.Errorf("unable to locate .app bundle from %s", app.ExecutablePath)
|
|
}
|
|
current = parent
|
|
}
|
|
}
|