Inital commit
This commit is contained in:
12
wails/BUILD.bazel
Normal file
12
wails/BUILD.bazel
Normal file
@@ -0,0 +1,12 @@
|
||||
load(":private/toolchain.bzl", "wails_toolchain")
|
||||
|
||||
toolchain_type(
|
||||
name = "toolchain_type",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
exports_files([
|
||||
"defs.bzl",
|
||||
"README.md",
|
||||
])
|
||||
|
||||
28
wails/README.md
Normal file
28
wails/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# rules_wails core
|
||||
|
||||
Core Bazel rules for Wails applications.
|
||||
|
||||
Public API:
|
||||
|
||||
- `wails_toolchain`
|
||||
- `wails_build_assets`
|
||||
- `wails_generate_bindings`
|
||||
- `wails_run`
|
||||
- `wails_app`
|
||||
|
||||
The caller provides a registered toolchain with:
|
||||
|
||||
- `wails`: an executable target used to invoke Wails.
|
||||
- `go`: an executable file used by that Wails wrapper for hermetic `go run` execution.
|
||||
|
||||
Notes:
|
||||
|
||||
- Core action helpers are Go binaries.
|
||||
- `wails_run` launches the built application but does not build the Go binary for you.
|
||||
- `wails_generate_bindings` stages the Bazel package tree so bindings generation runs from the correct module root.
|
||||
|
||||
Load with:
|
||||
|
||||
```starlark
|
||||
load("@rules_wails//wails:defs.bzl", "wails_build_assets", "wails_generate_bindings", "wails_run", "wails_toolchain")
|
||||
```
|
||||
17
wails/defs.bzl
Normal file
17
wails/defs.bzl
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Public API surface for core Wails rules."""
|
||||
|
||||
load(":private/build_assets.bzl", _wails_build_assets = "wails_build_assets")
|
||||
load(":private/generate_bindings.bzl", _wails_generate_bindings = "wails_generate_bindings")
|
||||
load(":private/macros.bzl", _wails_app = "wails_app")
|
||||
load(":private/run.bzl", _wails_run = "wails_run")
|
||||
load(":private/toolchain.bzl", _WailsToolchainInfo = "WailsToolchainInfo", _wails_toolchain = "wails_toolchain")
|
||||
|
||||
visibility("public")
|
||||
|
||||
WailsToolchainInfo = _WailsToolchainInfo
|
||||
wails_toolchain = _wails_toolchain
|
||||
wails_build_assets = _wails_build_assets
|
||||
wails_generate_bindings = _wails_generate_bindings
|
||||
wails_run = _wails_run
|
||||
wails_app = _wails_app
|
||||
|
||||
58
wails/private/build_assets.bzl
Normal file
58
wails/private/build_assets.bzl
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Rule for generating Wails build assets."""
|
||||
|
||||
load(":private/common.bzl", "write_manifest")
|
||||
|
||||
def _wails_build_assets_impl(ctx):
|
||||
toolchain = ctx.toolchains["//wails:toolchain_type"].wails
|
||||
out_dir = ctx.actions.declare_directory(ctx.label.name)
|
||||
manifest = write_manifest(ctx, ctx.label.name + ".manifest", ctx.files.srcs, ctx.attr.strip_prefix)
|
||||
|
||||
args = ctx.actions.args()
|
||||
args.add("--app-name", ctx.attr.app_name)
|
||||
args.add("--binary-name", ctx.attr.binary_name)
|
||||
args.add("--config-file", ctx.attr.config_file)
|
||||
args.add("--manifest", manifest.path)
|
||||
args.add("--out", out_dir.path)
|
||||
args.add("--wails", toolchain.executable.path)
|
||||
if ctx.attr.macos_minimum_system_version:
|
||||
args.add("--macos-minimum-system-version", ctx.attr.macos_minimum_system_version)
|
||||
|
||||
ctx.actions.run(
|
||||
executable = ctx.executable._tool,
|
||||
arguments = [args],
|
||||
inputs = depset(ctx.files.srcs + [manifest, toolchain.go_executable]),
|
||||
outputs = [out_dir],
|
||||
tools = [
|
||||
ctx.executable._tool,
|
||||
toolchain.files_to_run,
|
||||
],
|
||||
env = {
|
||||
"GO_BIN": toolchain.go_executable.path,
|
||||
},
|
||||
mnemonic = "WailsBuildAssets",
|
||||
progress_message = "Generating Wails build assets for %s" % ctx.label,
|
||||
)
|
||||
|
||||
return [DefaultInfo(files = depset([out_dir]))]
|
||||
|
||||
wails_build_assets = rule(
|
||||
implementation = _wails_build_assets_impl,
|
||||
doc = "Runs `wails update build-assets` in a staged build directory.",
|
||||
attrs = {
|
||||
"app_name": attr.string(mandatory = True),
|
||||
"binary_name": attr.string(mandatory = True),
|
||||
"config_file": attr.string(default = "config.yml"),
|
||||
"macos_minimum_system_version": attr.string(default = ""),
|
||||
"srcs": attr.label_list(
|
||||
mandatory = True,
|
||||
allow_files = True,
|
||||
),
|
||||
"strip_prefix": attr.string(default = ""),
|
||||
"_tool": attr.label(
|
||||
default = "//wails/tools:build_assets_action",
|
||||
cfg = "exec",
|
||||
executable = True,
|
||||
),
|
||||
},
|
||||
toolchains = ["//wails:toolchain_type"],
|
||||
)
|
||||
58
wails/private/common.bzl
Normal file
58
wails/private/common.bzl
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Shared helpers for Wails rules."""
|
||||
|
||||
def _normalize_prefix(prefix):
|
||||
if not prefix or prefix == ".":
|
||||
return ""
|
||||
|
||||
normalized = prefix.strip("/")
|
||||
if not normalized:
|
||||
return ""
|
||||
|
||||
return normalized + "/"
|
||||
|
||||
def _manifest_rel_path(short_path, strip_prefix):
|
||||
normalized_prefix = _normalize_prefix(strip_prefix)
|
||||
rel_path = short_path
|
||||
|
||||
if normalized_prefix and rel_path.startswith(normalized_prefix):
|
||||
rel_path = rel_path[len(normalized_prefix):]
|
||||
|
||||
return rel_path
|
||||
|
||||
def write_manifest(ctx, name, files, strip_prefix = ""):
|
||||
manifest = ctx.actions.declare_file(name)
|
||||
lines = []
|
||||
|
||||
for src in files:
|
||||
lines.append("%s\t%s" % (src.path, _manifest_rel_path(src.short_path, strip_prefix)))
|
||||
|
||||
ctx.actions.write(
|
||||
output = manifest,
|
||||
content = "\n".join(lines) + "\n",
|
||||
)
|
||||
|
||||
return manifest
|
||||
|
||||
def bash_launcher(resolve_lines, command_lines):
|
||||
return """#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
runfiles_dir="${{RUNFILES_DIR:-$0.runfiles}}"
|
||||
export RUNFILES_DIR="${{runfiles_dir}}"
|
||||
export RUNFILES="${{runfiles_dir}}"
|
||||
resolve_runfile() {{
|
||||
local short_path="$1"
|
||||
|
||||
if [[ "$short_path" == ../* ]]; then
|
||||
echo "${{runfiles_dir}}/${{short_path#../}}"
|
||||
else
|
||||
echo "${{runfiles_dir}}/_main/${{short_path}}"
|
||||
fi
|
||||
}}
|
||||
|
||||
{resolve_lines}
|
||||
{command_lines}
|
||||
""".format(
|
||||
resolve_lines = resolve_lines.strip(),
|
||||
command_lines = command_lines.strip(),
|
||||
)
|
||||
205
wails/private/generate_bindings.bzl
Normal file
205
wails/private/generate_bindings.bzl
Normal file
@@ -0,0 +1,205 @@
|
||||
"""Rules for Wails binding generation."""
|
||||
|
||||
load(":private/common.bzl", "bash_launcher", "write_manifest")
|
||||
|
||||
def _normalize_relative_path(path):
|
||||
if not path or path == ".":
|
||||
return ""
|
||||
|
||||
return path.strip("/")
|
||||
|
||||
def _join_relative_path(base, child):
|
||||
normalized_base = _normalize_relative_path(base)
|
||||
normalized_child = _normalize_relative_path(child)
|
||||
|
||||
if normalized_base and normalized_child:
|
||||
return normalized_base + "/" + normalized_child
|
||||
if normalized_base:
|
||||
return normalized_base
|
||||
return normalized_child
|
||||
|
||||
def _package_glob_patterns(package_dir, out_dir):
|
||||
normalized = package_dir.strip("./")
|
||||
generated_dir = _join_relative_path(normalized, out_dir)
|
||||
|
||||
exclude_patterns = ["**/node_modules/**", "**/dist/**"]
|
||||
if generated_dir:
|
||||
exclude_patterns.append(generated_dir + "/**")
|
||||
|
||||
if not normalized:
|
||||
return ("**", exclude_patterns)
|
||||
|
||||
return (
|
||||
normalized + "/**",
|
||||
exclude_patterns + [
|
||||
normalized + "/node_modules/**",
|
||||
normalized + "/dist/**",
|
||||
],
|
||||
)
|
||||
|
||||
def _workspace_package_dir(label_package, package_dir):
|
||||
if label_package and package_dir and package_dir != ".":
|
||||
return label_package + "/" + package_dir
|
||||
if label_package:
|
||||
return label_package
|
||||
return package_dir
|
||||
|
||||
def _wails_generate_bindings_impl(ctx):
|
||||
toolchain = ctx.toolchains["//wails:toolchain_type"].wails
|
||||
out_tree = ctx.actions.declare_directory(ctx.label.name + "_out")
|
||||
workspace_package_dir = _workspace_package_dir(ctx.label.package, ctx.attr.package_dir or ".")
|
||||
manifest = write_manifest(
|
||||
ctx,
|
||||
ctx.label.name + ".manifest",
|
||||
ctx.files.srcs,
|
||||
workspace_package_dir,
|
||||
)
|
||||
|
||||
build_args = ctx.actions.args()
|
||||
build_args.add("--clean=%s" % ("true" if ctx.attr.clean else "false"))
|
||||
build_args.add("--manifest", manifest.path)
|
||||
build_args.add("--mode", "action")
|
||||
build_args.add("--out", out_tree.path)
|
||||
build_args.add("--out-dir", ctx.attr.out_dir)
|
||||
build_args.add("--ts=%s" % ("true" if ctx.attr.ts else "false"))
|
||||
build_args.add("--wails", toolchain.executable.path)
|
||||
for extra_arg in ctx.attr.extra_args:
|
||||
build_args.add("--extra-arg", extra_arg)
|
||||
|
||||
ctx.actions.run(
|
||||
executable = ctx.executable._tool,
|
||||
arguments = [build_args],
|
||||
inputs = depset(ctx.files.srcs + [manifest, toolchain.go_executable]),
|
||||
outputs = [out_tree],
|
||||
tools = [
|
||||
ctx.executable._tool,
|
||||
toolchain.files_to_run,
|
||||
],
|
||||
env = {
|
||||
"GO_BIN": toolchain.go_executable.path,
|
||||
},
|
||||
mnemonic = "WailsGenerateBindings",
|
||||
progress_message = "Generating Wails bindings for %s" % ctx.label,
|
||||
)
|
||||
|
||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||
resolve_lines = """
|
||||
tool="$(resolve_runfile "{tool_short_path}")"
|
||||
wails="$(resolve_runfile "{wails_short_path}")"
|
||||
go_bin="$(resolve_runfile "{go_short_path}")"
|
||||
workspace_root="${{BUILD_WORKSPACE_DIRECTORY:-}}"
|
||||
if [[ -z "$workspace_root" ]]; then
|
||||
echo "BUILD_WORKSPACE_DIRECTORY is required for bazel run bindings generation" >&2
|
||||
exit 1
|
||||
fi
|
||||
workspace_package_dir="{workspace_package_dir}"
|
||||
if [[ -n "$workspace_package_dir" && "$workspace_package_dir" != "." ]]; then
|
||||
workspace_package_dir="${{workspace_root}}/${{workspace_package_dir}}"
|
||||
else
|
||||
workspace_package_dir="${{workspace_root}}"
|
||||
fi
|
||||
""".format(
|
||||
tool_short_path = ctx.executable._tool.short_path,
|
||||
wails_short_path = toolchain.executable.short_path,
|
||||
go_short_path = toolchain.go_executable.short_path,
|
||||
workspace_package_dir = workspace_package_dir or ".",
|
||||
)
|
||||
|
||||
extra_args_lines = "\n".join([
|
||||
'cmd+=(--extra-arg %s)' % _shell_quote(arg)
|
||||
for arg in ctx.attr.extra_args
|
||||
])
|
||||
command_lines = """
|
||||
cmd=(
|
||||
"$tool"
|
||||
--clean={clean}
|
||||
--mode workspace
|
||||
--out-dir {out_dir}
|
||||
--package-dir "$workspace_package_dir"
|
||||
--ts={ts}
|
||||
--wails "$wails"
|
||||
)
|
||||
{extra_args_lines}
|
||||
export GO_BIN="$go_bin"
|
||||
exec "${{cmd[@]}}"
|
||||
""".format(
|
||||
clean = "true" if ctx.attr.clean else "false",
|
||||
out_dir = _shell_quote(ctx.attr.out_dir),
|
||||
ts = "true" if ctx.attr.ts else "false",
|
||||
extra_args_lines = extra_args_lines,
|
||||
)
|
||||
|
||||
ctx.actions.write(
|
||||
output = launcher,
|
||||
is_executable = True,
|
||||
content = bash_launcher(resolve_lines, command_lines),
|
||||
)
|
||||
|
||||
runfiles = ctx.runfiles(
|
||||
files = [
|
||||
ctx.executable._tool,
|
||||
toolchain.executable,
|
||||
toolchain.go_executable,
|
||||
],
|
||||
transitive_files = depset(transitive = [
|
||||
ctx.attr._tool[DefaultInfo].default_runfiles.files,
|
||||
toolchain.default_runfiles.files,
|
||||
toolchain.go_default_runfiles.files,
|
||||
]),
|
||||
)
|
||||
|
||||
return [
|
||||
DefaultInfo(
|
||||
executable = launcher,
|
||||
files = depset([out_tree]),
|
||||
runfiles = runfiles,
|
||||
),
|
||||
]
|
||||
|
||||
def _shell_quote(value):
|
||||
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||
|
||||
_wails_generate_bindings_rule = rule(
|
||||
implementation = _wails_generate_bindings_impl,
|
||||
attrs = {
|
||||
"clean": attr.bool(default = True),
|
||||
"extra_args": attr.string_list(),
|
||||
"out_dir": attr.string(mandatory = True),
|
||||
"package_dir": attr.string(default = "."),
|
||||
"srcs": attr.label_list(
|
||||
allow_files = True,
|
||||
mandatory = True,
|
||||
),
|
||||
"ts": attr.bool(default = True),
|
||||
"_tool": attr.label(
|
||||
default = "//wails/tools:generate_bindings_action",
|
||||
cfg = "exec",
|
||||
executable = True,
|
||||
),
|
||||
},
|
||||
executable = True,
|
||||
toolchains = ["//wails:toolchain_type"],
|
||||
)
|
||||
|
||||
def wails_generate_bindings(
|
||||
name,
|
||||
out_dir,
|
||||
package_dir = ".",
|
||||
clean = True,
|
||||
ts = True,
|
||||
extra_args = None,
|
||||
tags = None,
|
||||
visibility = None):
|
||||
include_pattern, exclude_patterns = _package_glob_patterns(package_dir, out_dir)
|
||||
|
||||
_wails_generate_bindings_rule(
|
||||
name = name,
|
||||
clean = clean,
|
||||
extra_args = extra_args or [],
|
||||
out_dir = out_dir,
|
||||
package_dir = package_dir,
|
||||
srcs = native.glob([include_pattern], exclude = exclude_patterns),
|
||||
tags = tags,
|
||||
visibility = visibility,
|
||||
ts = ts,
|
||||
)
|
||||
27
wails/private/macros.bzl
Normal file
27
wails/private/macros.bzl
Normal file
@@ -0,0 +1,27 @@
|
||||
"""Convenience macros for Wails rules."""
|
||||
|
||||
load(":private/generate_bindings.bzl", "wails_generate_bindings")
|
||||
load(":private/run.bzl", "wails_run")
|
||||
|
||||
def wails_app(name, binary, build_assets, bindings = None, icon = None, visibility = None, tags = None):
|
||||
wails_run(
|
||||
name = name + "_run",
|
||||
binary = binary,
|
||||
build_assets = build_assets,
|
||||
icon = icon,
|
||||
tags = tags,
|
||||
visibility = visibility,
|
||||
)
|
||||
|
||||
if bindings:
|
||||
wails_generate_bindings(
|
||||
name = name + "_bindings",
|
||||
out_dir = bindings["out_dir"],
|
||||
package_dir = bindings.get("package_dir", "."),
|
||||
clean = bindings.get("clean", True),
|
||||
ts = bindings.get("ts", True),
|
||||
extra_args = bindings.get("extra_args", []),
|
||||
tags = tags,
|
||||
visibility = visibility,
|
||||
)
|
||||
|
||||
87
wails/private/run.bzl
Normal file
87
wails/private/run.bzl
Normal file
@@ -0,0 +1,87 @@
|
||||
"""Runnable Wails app launcher rule."""
|
||||
|
||||
load(":private/common.bzl", "bash_launcher")
|
||||
|
||||
def _wails_run_impl(ctx):
|
||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||
|
||||
resolve_lines = """
|
||||
tool="$(resolve_runfile "{tool_short_path}")"
|
||||
binary="$(resolve_runfile "{binary_short_path}")"
|
||||
build_assets="$(resolve_runfile "{build_assets_short_path}")"
|
||||
icon=""
|
||||
if [[ -n "{icon_short_path}" ]]; then
|
||||
icon="$(resolve_runfile "{icon_short_path}")"
|
||||
fi
|
||||
""".format(
|
||||
tool_short_path = ctx.executable._tool.short_path,
|
||||
binary_short_path = ctx.executable.binary.short_path,
|
||||
build_assets_short_path = ctx.files.build_assets[0].short_path,
|
||||
icon_short_path = ctx.file.icon.short_path if ctx.file.icon else "",
|
||||
)
|
||||
|
||||
command_lines = """
|
||||
exec "$tool" \
|
||||
--binary "$binary" \
|
||||
--build-assets "$build_assets" \
|
||||
--frontend-url {frontend_url} \
|
||||
--icon "$icon" \
|
||||
--mode {mode}
|
||||
""".format(
|
||||
frontend_url = _shell_quote(ctx.attr.frontend_url),
|
||||
mode = _shell_quote(ctx.attr.mode),
|
||||
)
|
||||
|
||||
ctx.actions.write(
|
||||
output = launcher,
|
||||
is_executable = True,
|
||||
content = bash_launcher(resolve_lines, command_lines),
|
||||
)
|
||||
|
||||
transitive_files = [
|
||||
ctx.attr._tool[DefaultInfo].default_runfiles.files,
|
||||
ctx.attr.binary[DefaultInfo].default_runfiles.files,
|
||||
]
|
||||
|
||||
runfiles = ctx.runfiles(
|
||||
files = [
|
||||
ctx.executable._tool,
|
||||
ctx.executable.binary,
|
||||
] + ctx.files.build_assets + ([ctx.file.icon] if ctx.file.icon else []),
|
||||
transitive_files = depset(transitive = transitive_files),
|
||||
)
|
||||
|
||||
return [DefaultInfo(executable = launcher, runfiles = runfiles)]
|
||||
|
||||
def _shell_quote(value):
|
||||
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||
|
||||
wails_run = rule(
|
||||
implementation = _wails_run_impl,
|
||||
doc = "Creates a runnable target that launches a Wails application.",
|
||||
attrs = {
|
||||
"binary": attr.label(
|
||||
mandatory = True,
|
||||
executable = True,
|
||||
cfg = "target",
|
||||
),
|
||||
"build_assets": attr.label(
|
||||
mandatory = True,
|
||||
allow_files = True,
|
||||
),
|
||||
"frontend_url": attr.string(default = "http://127.0.0.1:9245"),
|
||||
"icon": attr.label(
|
||||
allow_single_file = True,
|
||||
),
|
||||
"mode": attr.string(
|
||||
default = "run",
|
||||
values = ["dev", "run"],
|
||||
),
|
||||
"_tool": attr.label(
|
||||
default = "//wails/tools:launch_app",
|
||||
cfg = "exec",
|
||||
executable = True,
|
||||
),
|
||||
},
|
||||
executable = True,
|
||||
)
|
||||
48
wails/private/toolchain.bzl
Normal file
48
wails/private/toolchain.bzl
Normal file
@@ -0,0 +1,48 @@
|
||||
"""Toolchain definitions for Wails."""
|
||||
|
||||
WailsToolchainInfo = provider(
|
||||
doc = "Caller-supplied Wails executable, Go executable, and runfiles.",
|
||||
fields = {
|
||||
"default_runfiles": "Runfiles for the Wails executable.",
|
||||
"go_default_runfiles": "Runfiles for the Go executable.",
|
||||
"go_executable": "Executable file for the Go tool.",
|
||||
"executable": "Executable file for the Wails tool.",
|
||||
"files_to_run": "FilesToRunProvider for the Wails tool.",
|
||||
},
|
||||
)
|
||||
|
||||
def _wails_toolchain_impl(ctx):
|
||||
wails_default_info = ctx.attr.wails[DefaultInfo]
|
||||
go_default_info = ctx.attr.go[DefaultInfo]
|
||||
|
||||
return [
|
||||
platform_common.ToolchainInfo(
|
||||
wails = WailsToolchainInfo(
|
||||
default_runfiles = wails_default_info.default_runfiles,
|
||||
go_default_runfiles = go_default_info.default_runfiles,
|
||||
go_executable = ctx.file.go,
|
||||
executable = ctx.executable.wails,
|
||||
files_to_run = wails_default_info.files_to_run,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
wails_toolchain = rule(
|
||||
implementation = _wails_toolchain_impl,
|
||||
attrs = {
|
||||
"go": attr.label(
|
||||
mandatory = True,
|
||||
cfg = "exec",
|
||||
allow_single_file = True,
|
||||
doc = "Executable file used for hermetic `go run` invocations inside the Wails tool.",
|
||||
),
|
||||
"wails": attr.label(
|
||||
mandatory = True,
|
||||
cfg = "exec",
|
||||
executable = True,
|
||||
allow_files = True,
|
||||
doc = "Executable target used to invoke Wails.",
|
||||
),
|
||||
},
|
||||
doc = "Registers caller-supplied Wails and Go executables as a Bazel toolchain.",
|
||||
)
|
||||
25
wails/tools/BUILD.bazel
Normal file
25
wails/tools/BUILD.bazel
Normal 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"],
|
||||
)
|
||||
225
wails/tools/build_assets_action.go
Normal file
225
wails/tools/build_assets_action.go
Normal 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
|
||||
}
|
||||
239
wails/tools/generate_bindings_action.go
Normal file
239
wails/tools/generate_bindings_action.go
Normal 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
139
wails/tools/launch_app.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user