Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 697c322

Browse files
authored
fix: support optimized Bazel builds for Tauri desktop app (#38)
Tauri's generate_context!() proc macro generates struct literals whose shape depends on cfg(debug_assertions) in the host config, but the code compiles in the target config. With -c opt the target loses debug_assertions while the host (fastbuild from .bazelrc) keeps them, causing missing type/field errors. Add --config=opt to .bazelrc that sets both compilation_mode and host_compilation_mode to opt. Gate devtools menu item and handler behind #[cfg(debug_assertions)] since those APIs are unavailable in release.
1 parent b708dc3 commit 697c322

6 files changed

Lines changed: 126 additions & 22 deletions

File tree

.bazelrc

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,8 @@ build:clippy --@rules_rust//:clippy_flags=-Dwarnings
1818
# Running them locally prevents sandbox-path leakage into downstream actions.
1919
build --strategy=CargoBuildScriptRun=local
2020

21-
# Tauri proc-macro cfg consistency
22-
# Tauri's ResolvedCommand has #[cfg(debug_assertions)] fields. Proc macros
23-
# run in the exec config (default: opt → no debug_assertions), but the target
24-
# uses fastbuild (debug_assertions ON). This mismatch causes the proc macro to
25-
# generate struct literals missing fields the target compilation expects.
26-
# Compiling exec tools in fastbuild mode keeps cfg(debug_assertions) consistent.
27-
build --host_compilation_mode=fastbuild
21+
# Optimized builds
22+
build:opt --compilation_mode=opt
2823

2924
# Coverage
3025
# Filter to only instrument our source code, not external crates or test harnesses

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ All Rust code uses edition 2024. Cargo defaults to edition 2021 for `cargo check
4141
- `cargo check -p crab_city_desktop` — quick compile check
4242
- `cargo test -p crab_city_desktop` — run unit tests
4343
- `cd packages/crab_city_desktop && cargo tauri dev --config tauri.dev.conf.json` — launch desktop app with embedded server (auto-starts Vite dev server)
44-
- `bazel build //packages/crab_city_desktop:macos_app` — build macOS `.app` bundle with embedded server
44+
- `bazel build //packages/crab_city_desktop:macos_app` — build macOS `.app` bundle (debug)
45+
- `bazel build --config=opt //packages/crab_city_desktop:macos_app` — build optimized `.app` bundle
4546

4647
**Dev workflow** (single terminal): `cd packages/crab_city_desktop && cargo tauri dev --config tauri.dev.conf.json` — the Tauri app starts an embedded server in-process, and Vite's dev proxy discovers it automatically via the `daemon.port` file. The `--config` flag merges `tauri.dev.conf.json` (devUrl + beforeDevCommand) into the base config. The base `tauri.conf.json` has no dev URL — production builds never reference external dev servers.
4748

packages/crab_city_desktop/BUILD.bazel

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
22
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
33
load(":macos_app.bzl", "macos_app")
4+
load(":tauri_transition.bzl", "tauri_binary", "tauri_test")
45

56
package(default_visibility = ["//visibility:public"])
67

@@ -45,7 +46,7 @@ cargo_build_script(
4546
)
4647

4748
rust_binary(
48-
name = "crab_city_desktop",
49+
name = "_crab_city_desktop_raw",
4950
srcs = glob(["src/**/*.rs"]),
5051
compile_data = _TAURI_DATA,
5152
edition = "2024",
@@ -54,8 +55,13 @@ rust_binary(
5455
"CARGO_MANIFEST_DIR": "packages/crab_city_desktop",
5556
},
5657
# no-sandbox: tauri proc macros read config and icon files from disk
57-
tags = ["no-sandbox"],
58+
# manual: must be built via tauri_binary (which applies cfg transition)
59+
tags = [
60+
"manual",
61+
"no-sandbox",
62+
],
5863
target_compatible_with = ["@platforms//os:macos"],
64+
visibility = ["//visibility:private"],
5965
deps = [
6066
":build_script",
6167
"//packages/crab_city:crab_city_lib_embedded",
@@ -71,16 +77,33 @@ rust_binary(
7177
],
7278
)
7379

80+
tauri_binary(
81+
name = "crab_city_desktop",
82+
binary = ":_crab_city_desktop_raw",
83+
target_compatible_with = ["@platforms//os:macos"],
84+
)
85+
7486
rust_test(
75-
name = "crab_city_desktop_test",
87+
name = "_crab_city_desktop_test_raw",
7688
compile_data = _TAURI_DATA,
77-
crate = ":crab_city_desktop",
89+
crate = ":_crab_city_desktop_raw",
7890
edition = "2024",
7991
rustc_env = {
8092
"CARGO_MANIFEST_DIR": "packages/crab_city_desktop",
8193
},
82-
tags = ["no-sandbox"],
94+
# manual: must be built via tauri_test (which applies cfg transition)
95+
tags = [
96+
"manual",
97+
"no-sandbox",
98+
],
99+
target_compatible_with = ["@platforms//os:macos"],
100+
visibility = ["//visibility:private"],
101+
)
102+
103+
tauri_test(
104+
name = "crab_city_desktop_test",
83105
target_compatible_with = ["@platforms//os:macos"],
106+
test = ":_crab_city_desktop_test_raw",
84107
)
85108

86109
macos_app(

packages/crab_city_desktop/CLAUDE.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ Tauri's `generate_context!()` proc macro writes cache files to `OUT_DIR` during
4141

4242
Frontend assets (`frontendDist`) are brotli-compressed into `$OUT_DIR/tauri-codegen-assets/{blake3_hash}.{ext}`. The `precreate_frontend_cache()` function in `build.rs` replicates this exactly so the proc macro's write is a no-op in Bazel.
4343

44-
Additionally, `ResolvedCommand` has `#[cfg(debug_assertions)]` fields. Bazel compiles proc macros in exec config (default: `opt`, no debug_assertions) but targets in `fastbuild` (with debug_assertions). This mismatch is fixed by `--host_compilation_mode=fastbuild` in `.bazelrc`.
44+
Additionally, `ResolvedCommand` has `#[cfg(debug_assertions)]` fields. Bazel compiles proc macros in exec config (default: `opt`, no debug_assertions) but targets in `fastbuild` (with debug_assertions). This mismatch is fixed by a Starlark transition in `tauri_transition.bzl` that forces `host_compilation_mode` to match `compilation_mode`. The `tauri_binary` and `tauri_test` wrapper rules apply this transition — the raw `rust_binary` and `rust_test` targets are tagged `manual` and should not be built directly.
45+
46+
DevTools APIs (`is_devtools_open()`, `open_devtools()`, `close_devtools()`) are only available with `debug_assertions`. The devtools menu item and handler are gated behind `#[cfg(debug_assertions)]`.
4547

4648
## Dev Workflow
4749

@@ -80,7 +82,10 @@ When the external server dies, the health monitor navigates back to `tauri://loc
8082
## Release Build (macOS .app bundle)
8183

8284
```sh
83-
bazel build //packages/crab_city_desktop:macos_app
85+
bazel build //packages/crab_city_desktop:macos_app # debug
86+
bazel build --config=opt //packages/crab_city_desktop:macos_app # optimized
8487
```
8588

89+
The `tauri_binary` wrapper rule applies a Starlark transition that keeps `host_compilation_mode` in sync with `compilation_mode`, so both `-c opt` and `--config=opt` work correctly.
90+
8691
Produces `CrabCity.app` with the Tauri binary (which includes the embedded server) in `Contents/MacOS/`. No sidecar binary needed. The `macos_app` rule in `macos_app.bzl` uses a tree artifact (directory output) to assemble the bundle structure. Code signing and notarization are deferred to when public distribution is needed.

packages/crab_city_desktop/src/main.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ fn setup_menu(app: &tauri::App) -> tauri::Result<()> {
286286
.accelerator("CmdOrCtrl+R")
287287
.build(app)?;
288288

289+
#[cfg(debug_assertions)]
289290
let devtools_item = MenuItemBuilder::new("Toggle Developer Tools")
290291
.id("devtools")
291292
.accelerator("CmdOrCtrl+Alt+I")
@@ -320,12 +321,10 @@ fn setup_menu(app: &tauri::App) -> tauri::Result<()> {
320321
.select_all()
321322
.build()?;
322323

323-
let view_submenu = SubmenuBuilder::new(app, "View")
324-
.item(&reload_item)
325-
.item(&devtools_item)
326-
.separator()
327-
.fullscreen()
328-
.build()?;
324+
let view_submenu_builder = SubmenuBuilder::new(app, "View").item(&reload_item);
325+
#[cfg(debug_assertions)]
326+
let view_submenu_builder = view_submenu_builder.item(&devtools_item);
327+
let view_submenu = view_submenu_builder.separator().fullscreen().build()?;
329328

330329
let window_submenu = SubmenuBuilder::new(app, "Window")
331330
.minimize()
@@ -356,7 +355,9 @@ fn setup_menu(app: &tauri::App) -> tauri::Result<()> {
356355
}
357356
}
358357
}
359-
"devtools" => {
358+
"devtools" =>
359+
{
360+
#[cfg(debug_assertions)]
360361
if let Some(window) = app_handle.get_webview_window("main") {
361362
if window.is_devtools_open() {
362363
window.close_devtools();
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""Bazel transition that forces host_compilation_mode to match compilation_mode.
2+
3+
Tauri's proc macros use #[cfg(debug_assertions)] on struct fields shared between
4+
codegen output and runtime (e.g. ResolvedCommand). The proc macro runs in the
5+
host/exec configuration while the generated code compiles in the target
6+
configuration. If those disagree on debug_assertions the build breaks. This
7+
transition keeps them in sync, scoped to Tauri targets only.
8+
"""
9+
10+
def _match_host_compilation_mode_impl(settings, _attr):
11+
return {"//command_line_option:host_compilation_mode": settings["//command_line_option:compilation_mode"]}
12+
13+
_match_host_compilation_mode = transition(
14+
implementation = _match_host_compilation_mode_impl,
15+
inputs = ["//command_line_option:compilation_mode"],
16+
outputs = ["//command_line_option:host_compilation_mode"],
17+
)
18+
19+
_TRANSITION_ATTRS = {
20+
"_allowlist_function_transition": attr.label(
21+
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
22+
),
23+
}
24+
25+
# --- tauri_binary ---------------------------------------------------------
26+
27+
def _tauri_binary_impl(ctx):
28+
bin_info = ctx.attr.binary[0][DefaultInfo]
29+
real_executable = bin_info.files_to_run.executable
30+
31+
# Executable rules require the executable to be created by the rule itself.
32+
out = ctx.actions.declare_file(ctx.label.name)
33+
ctx.actions.symlink(output = out, target_file = real_executable)
34+
35+
return [DefaultInfo(
36+
files = depset([out]),
37+
runfiles = ctx.runfiles(files = [real_executable]).merge(bin_info.default_runfiles),
38+
executable = out,
39+
)]
40+
41+
tauri_binary = rule(
42+
implementation = _tauri_binary_impl,
43+
executable = True,
44+
attrs = dict({
45+
"binary": attr.label(
46+
mandatory = True,
47+
executable = True,
48+
cfg = _match_host_compilation_mode,
49+
),
50+
}, **_TRANSITION_ATTRS),
51+
)
52+
53+
# --- tauri_test -----------------------------------------------------------
54+
55+
def _tauri_test_impl(ctx):
56+
test_info = ctx.attr.test[0][DefaultInfo]
57+
real_executable = test_info.files_to_run.executable
58+
59+
# Symlink so the test executable is owned by this rule.
60+
out = ctx.actions.declare_file(ctx.label.name)
61+
ctx.actions.symlink(output = out, target_file = real_executable)
62+
63+
return [DefaultInfo(
64+
files = depset([out]),
65+
runfiles = ctx.runfiles(files = [real_executable]).merge(test_info.default_runfiles),
66+
executable = out,
67+
)]
68+
69+
tauri_test = rule(
70+
implementation = _tauri_test_impl,
71+
test = True,
72+
attrs = dict({
73+
"test": attr.label(
74+
mandatory = True,
75+
executable = True,
76+
cfg = _match_host_compilation_mode,
77+
),
78+
}, **_TRANSITION_ATTRS),
79+
)

0 commit comments

Comments
 (0)