Inital commit

This commit is contained in:
eric
2026-03-12 18:58:43 +01:00
commit 8555b02752
36 changed files with 3312 additions and 0 deletions

25
wails/tools/BUILD.bazel Normal file
View File

@@ -0,0 +1,25 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary")
go_binary(
name = "build_assets_action",
srcs = ["build_assets_action.go"],
importpath = "github.com/Eriyc/rules_wails/wails/tools/build_assets_action",
pure = "off",
visibility = ["//visibility:public"],
)
go_binary(
name = "generate_bindings_action",
srcs = ["generate_bindings_action.go"],
importpath = "github.com/Eriyc/rules_wails/wails/tools/generate_bindings_action",
pure = "off",
visibility = ["//visibility:public"],
)
go_binary(
name = "launch_app",
srcs = ["launch_app.go"],
importpath = "github.com/Eriyc/rules_wails/wails/tools/launch_app",
pure = "off",
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,225 @@
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
)
func main() {
var appName string
var binaryName string
var configFile string
var macOSMinimumSystemVersion string
var manifestPath string
var outDir string
var wailsPath string
flag.StringVar(&appName, "app-name", "", "")
flag.StringVar(&binaryName, "binary-name", "", "")
flag.StringVar(&configFile, "config-file", "config.yml", "")
flag.StringVar(&macOSMinimumSystemVersion, "macos-minimum-system-version", "", "")
flag.StringVar(&manifestPath, "manifest", "", "")
flag.StringVar(&outDir, "out", "", "")
flag.StringVar(&wailsPath, "wails", "", "")
flag.Parse()
require(manifestPath != "", "missing --manifest")
require(outDir != "", "missing --out")
require(wailsPath != "", "missing --wails")
require(appName != "", "missing --app-name")
require(binaryName != "", "missing --binary-name")
var err error
wailsPath, err = filepath.Abs(wailsPath)
must(err)
must(resolveGoEnvToAbsolutePath())
tempRoot, err := os.MkdirTemp("", "rules-wails-build-assets-*")
must(err)
defer func() {
_ = os.Chmod(tempRoot, 0o755)
_ = os.RemoveAll(tempRoot)
}()
workDir := filepath.Join(tempRoot, "work")
homeDir := filepath.Join(tempRoot, "home")
must(os.MkdirAll(workDir, 0o755))
must(os.MkdirAll(homeDir, 0o755))
must(stageManifest(manifestPath, workDir))
command := exec.Command(
wailsPath,
"update",
"build-assets",
"-name", appName,
"-binaryname", binaryName,
"-config", filepath.Join(workDir, configFile),
"-dir", workDir,
)
command.Dir = workDir
command.Env = append(os.Environ(),
"HOME="+homeDir,
"LC_ALL=C",
"TZ=UTC",
)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
must(command.Run())
updateMacOSMinimumVersion(filepath.Join(workDir, "darwin", "Info.plist"), macOSMinimumSystemVersion)
updateMacOSMinimumVersion(filepath.Join(workDir, "darwin", "Info.dev.plist"), macOSMinimumSystemVersion)
must(os.RemoveAll(outDir))
must(os.MkdirAll(outDir, 0o755))
must(copyTree(workDir, outDir))
}
func resolveGoEnvToAbsolutePath() error {
goBinary := os.Getenv("GO_BIN")
if goBinary == "" || filepath.IsAbs(goBinary) {
return nil
}
absolutePath, err := filepath.Abs(goBinary)
if err != nil {
return err
}
return os.Setenv("GO_BIN", absolutePath)
}
func updateMacOSMinimumVersion(path string, minimumVersion string) {
if minimumVersion == "" {
return
}
if _, err := os.Stat(path); err != nil {
return
}
if _, err := os.Stat("/usr/libexec/PlistBuddy"); err != nil {
return
}
setCommand := exec.Command("/usr/libexec/PlistBuddy", "-c", "Set :LSMinimumSystemVersion "+minimumVersion, path)
if err := setCommand.Run(); err == nil {
return
}
addCommand := exec.Command("/usr/libexec/PlistBuddy", "-c", "Add :LSMinimumSystemVersion string "+minimumVersion, path)
_ = addCommand.Run()
}
func stageManifest(manifestPath string, destinationRoot string) error {
entries, err := readManifest(manifestPath)
if err != nil {
return err
}
for _, entry := range entries {
destinationPath := filepath.Join(destinationRoot, entry.relativePath)
if err := os.MkdirAll(filepath.Dir(destinationPath), 0o755); err != nil {
return err
}
if err := copyFile(entry.sourcePath, destinationPath); err != nil {
return err
}
}
return nil
}
func readManifest(path string) ([]manifestEntry, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
entries := make([]manifestEntry, 0)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
parts := strings.SplitN(line, "\t", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid manifest line: %s", line)
}
entries = append(entries, manifestEntry{
sourcePath: parts[0],
relativePath: parts[1],
})
}
return entries, scanner.Err()
}
func copyTree(sourceRoot string, destinationRoot string) error {
return filepath.Walk(sourceRoot, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relativePath, err := filepath.Rel(sourceRoot, path)
if err != nil {
return err
}
if relativePath == "." {
return nil
}
destinationPath := filepath.Join(destinationRoot, relativePath)
if info.IsDir() {
return os.MkdirAll(destinationPath, 0o755)
}
if err := os.MkdirAll(filepath.Dir(destinationPath), 0o755); err != nil {
return err
}
return copyFile(path, destinationPath)
})
}
func copyFile(sourcePath string, destinationPath string) error {
sourceFile, err := os.Open(sourcePath)
if err != nil {
return err
}
defer sourceFile.Close()
destinationFile, err := os.Create(destinationPath)
if err != nil {
return err
}
defer destinationFile.Close()
if _, err := io.Copy(destinationFile, sourceFile); err != nil {
return err
}
return destinationFile.Chmod(0o644)
}
func must(err error) {
if err != nil {
panic(err)
}
}
func require(condition bool, message string) {
if !condition {
panic(message)
}
}
type manifestEntry struct {
sourcePath string
relativePath string
}

View File

@@ -0,0 +1,239 @@
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
)
type repeatedFlag []string
func (value *repeatedFlag) String() string {
return strings.Join(*value, ",")
}
func (value *repeatedFlag) Set(input string) error {
*value = append(*value, input)
return nil
}
func main() {
var clean bool
var extraArgs repeatedFlag
var manifestPath string
var mode string
var outDir string
var outputPath string
var packageDir string
var ts bool
var wailsPath string
flag.BoolVar(&clean, "clean", true, "")
flag.Var(&extraArgs, "extra-arg", "")
flag.StringVar(&manifestPath, "manifest", "", "")
flag.StringVar(&mode, "mode", "", "")
flag.StringVar(&outDir, "out-dir", "", "")
flag.StringVar(&outputPath, "out", "", "")
flag.StringVar(&packageDir, "package-dir", "", "")
flag.BoolVar(&ts, "ts", true, "")
flag.StringVar(&wailsPath, "wails", "", "")
flag.Parse()
require(mode != "", "missing --mode")
require(outDir != "", "missing --out-dir")
require(wailsPath != "", "missing --wails")
var err error
wailsPath, err = filepath.Abs(wailsPath)
must(err)
must(resolveGoEnvToAbsolutePath())
switch mode {
case "action":
require(manifestPath != "", "missing --manifest")
require(outputPath != "", "missing --out")
must(runActionMode(manifestPath, outputPath, outDir, wailsPath, clean, ts, extraArgs))
case "workspace":
require(packageDir != "", "missing --package-dir")
must(runWorkspaceMode(packageDir, outDir, wailsPath, clean, ts, extraArgs))
default:
panic("unsupported --mode")
}
}
func resolveGoEnvToAbsolutePath() error {
goBinary := os.Getenv("GO_BIN")
if goBinary == "" || filepath.IsAbs(goBinary) {
return nil
}
absolutePath, err := filepath.Abs(goBinary)
if err != nil {
return err
}
return os.Setenv("GO_BIN", absolutePath)
}
func runActionMode(manifestPath string, outputPath string, outDir string, wailsPath string, clean bool, ts bool, extraArgs []string) error {
tempRoot, err := os.MkdirTemp("", "rules-wails-bindings-*")
if err != nil {
return err
}
defer os.RemoveAll(tempRoot)
workDir := filepath.Join(tempRoot, "work")
if err := os.MkdirAll(workDir, 0o755); err != nil {
return err
}
if err := stageManifest(manifestPath, workDir); err != nil {
return err
}
if err := runBindings(workDir, outDir, wailsPath, clean, ts, extraArgs); err != nil {
return err
}
if err := os.RemoveAll(outputPath); err != nil {
return err
}
if err := os.MkdirAll(outputPath, 0o755); err != nil {
return err
}
return copyTree(filepath.Join(workDir, outDir), outputPath)
}
func runWorkspaceMode(packageDir string, outDir string, wailsPath string, clean bool, ts bool, extraArgs []string) error {
return runBindings(packageDir, outDir, wailsPath, clean, ts, extraArgs)
}
func runBindings(cwd string, outDir string, wailsPath string, clean bool, ts bool, extraArgs []string) error {
commandArgs := []string{"generate", "bindings", "-d", outDir}
if clean {
commandArgs = append(commandArgs, "-clean")
}
if ts {
commandArgs = append(commandArgs, "-ts")
}
commandArgs = append(commandArgs, extraArgs...)
command := exec.Command(wailsPath, commandArgs...)
command.Dir = cwd
command.Stdout = os.Stdout
command.Stderr = os.Stderr
return command.Run()
}
func stageManifest(manifestPath string, destinationRoot string) error {
entries, err := readManifest(manifestPath)
if err != nil {
return err
}
for _, entry := range entries {
destinationPath := filepath.Join(destinationRoot, entry.relativePath)
if err := os.MkdirAll(filepath.Dir(destinationPath), 0o755); err != nil {
return err
}
if err := copyFile(entry.sourcePath, destinationPath); err != nil {
return err
}
}
return nil
}
func readManifest(path string) ([]manifestEntry, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
entries := make([]manifestEntry, 0)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
parts := strings.SplitN(line, "\t", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid manifest line: %s", line)
}
entries = append(entries, manifestEntry{
sourcePath: parts[0],
relativePath: parts[1],
})
}
return entries, scanner.Err()
}
func copyTree(sourceRoot string, destinationRoot string) error {
return filepath.Walk(sourceRoot, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relativePath, err := filepath.Rel(sourceRoot, path)
if err != nil {
return err
}
if relativePath == "." {
return nil
}
destinationPath := filepath.Join(destinationRoot, relativePath)
if info.IsDir() {
return os.MkdirAll(destinationPath, 0o755)
}
if err := os.MkdirAll(filepath.Dir(destinationPath), 0o755); err != nil {
return err
}
return copyFile(path, destinationPath)
})
}
func copyFile(sourcePath string, destinationPath string) error {
sourceFile, err := os.Open(sourcePath)
if err != nil {
return err
}
defer sourceFile.Close()
destinationFile, err := os.Create(destinationPath)
if err != nil {
return err
}
defer destinationFile.Close()
if _, err := io.Copy(destinationFile, sourceFile); err != nil {
return err
}
return destinationFile.Chmod(0o644)
}
func must(err error) {
if err != nil {
panic(err)
}
}
func require(condition bool, message string) {
if !condition {
panic(message)
}
}
type manifestEntry struct {
sourcePath string
relativePath string
}

139
wails/tools/launch_app.go Normal file
View File

@@ -0,0 +1,139 @@
package main
import (
"flag"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
func main() {
var binaryPath string
var buildAssetsPath string
var frontendURL string
var iconPath string
var mode string
flag.StringVar(&binaryPath, "binary", "", "")
flag.StringVar(&buildAssetsPath, "build-assets", "", "")
flag.StringVar(&frontendURL, "frontend-url", "http://127.0.0.1:9245", "")
flag.StringVar(&iconPath, "icon", "", "")
flag.StringVar(&mode, "mode", "run", "")
flag.Parse()
require(binaryPath != "", "missing --binary")
require(buildAssetsPath != "", "missing --build-assets")
environment := os.Environ()
if mode == "dev" && os.Getenv("FRONTEND_DEVSERVER_URL") == "" {
environment = append(environment, "FRONTEND_DEVSERVER_URL="+frontendURL)
}
if runtime.GOOS == "darwin" {
os.Exit(runDarwin(binaryPath, buildAssetsPath, iconPath, mode, environment))
}
command := exec.Command(binaryPath)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
command.Stdin = os.Stdin
command.Env = environment
if err := command.Run(); err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
os.Exit(exitError.ExitCode())
}
panic(err)
}
}
func runDarwin(binaryPath string, buildAssetsPath string, iconPath string, mode string, environment []string) int {
appName := strings.TrimSuffix(filepath.Base(binaryPath), filepath.Ext(binaryPath))
if appName == "" {
appName = "wails-app"
}
appDir := filepath.Join(os.TempDir(), appName+"-bazel-"+mode)
defer os.RemoveAll(appDir)
appContents := filepath.Join(appDir, "Contents")
appMacOS := filepath.Join(appContents, "MacOS")
appResources := filepath.Join(appContents, "Resources")
appBinary := filepath.Join(appMacOS, appName)
must(os.MkdirAll(appMacOS, 0o755))
must(os.MkdirAll(appResources, 0o755))
must(copyFile(binaryPath, appBinary, 0o755))
for _, candidate := range []string{
filepath.Join(buildAssetsPath, "darwin", "Info.dev.plist"),
filepath.Join(buildAssetsPath, "darwin", "Info.plist"),
} {
if mode != "dev" && strings.HasSuffix(candidate, "Info.dev.plist") {
continue
}
if _, err := os.Stat(candidate); err == nil {
must(copyFile(candidate, filepath.Join(appContents, "Info.plist"), 0o644))
break
}
}
if iconPath != "" {
if _, err := os.Stat(iconPath); err == nil {
must(copyFile(iconPath, filepath.Join(appResources, filepath.Base(iconPath)), 0o644))
}
}
if _, err := os.Stat("/usr/bin/codesign"); err == nil {
codesign := exec.Command("/usr/bin/codesign", "--force", "--deep", "--sign", "-", appDir)
_ = codesign.Run()
}
command := exec.Command(appBinary)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
command.Stdin = os.Stdin
command.Env = environment
if err := command.Run(); err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
return exitError.ExitCode()
}
panic(err)
}
return 0
}
func copyFile(sourcePath string, destinationPath string, mode os.FileMode) error {
sourceFile, err := os.Open(sourcePath)
if err != nil {
return err
}
defer sourceFile.Close()
destinationFile, err := os.Create(destinationPath)
if err != nil {
return err
}
defer destinationFile.Close()
if _, err := io.Copy(destinationFile, sourceFile); err != nil {
return err
}
return destinationFile.Chmod(mode)
}
func must(err error) {
if err != nil {
panic(err)
}
}
func require(condition bool, message string) {
if !condition {
panic(message)
}
}