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

View 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
View 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(),
)

View 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
View 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
View 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,
)

View 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.",
)