Skip to content

Commit 731ad57

Browse files
committed
fix: add no-op export! macro for native-guest bindings
Native applications don't export WASM functions, so the export! macro should be a no-op. This fix: 1. Adds a public no-op export! macro to native-guest wrappers 2. Filters out the conflicting pub(crate) use export line from wit-bindgen 3. Re-enables _bindings_host targets that were temporarily disabled The solution was inspired by cpetig's symmetric wit-bindgen fork which handles dual native/WASM execution. Fixes the build error: "failed to resolve: could not find export in bindings" Tested with: - //examples/basic:hello_component_bindings_host - //examples/basic:hello_component - //test/export_macro:all
1 parent 01994f3 commit 731ad57

File tree

1 file changed

+111
-24
lines changed

1 file changed

+111
-24
lines changed

rust/rust_wasm_component_bindgen.bzl

Lines changed: 111 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,98 @@ def _generate_wrapper_impl(ctx):
8282
)
8383
)
8484

85-
wrapper_content = """// Generated wrapper for WIT bindings
85+
# Different wrapper content based on mode
86+
if ctx.attr.mode == "native-guest":
87+
wrapper_content = """// Generated wrapper for WIT bindings (native-guest mode)
88+
//
89+
// COMPATIBILITY: wit-bindgen CLI 0.44.0 - 0.46.0
90+
// This wrapper provides a wit_bindgen::rt module compatible with the CLI-generated code.
91+
// The runtime provides allocation helpers and cleanup guards expected by generated bindings.
92+
//
93+
// For native-guest mode, we also provide a no-op export! macro since native applications
94+
// don't need to export WASM functions.
95+
//
96+
// API provided:
97+
// - Cleanup::new(layout) -> (*mut u8, Option<CleanupGuard>)
98+
// - CleanupGuard (with Drop impl for deallocation)
99+
// - run_ctors_once() - no-op for native applications
100+
// - maybe_link_cabi_realloc() - no-op for native applications
101+
// - export! - no-op macro for native applications (does nothing)
102+
103+
// Suppress clippy warnings for generated code
104+
#![allow(clippy::all)]
105+
#![allow(unused_imports)]
106+
#![allow(dead_code)]
107+
108+
// Minimal wit_bindgen::rt runtime compatible with CLI-generated code
109+
pub mod wit_bindgen {
110+
pub mod rt {
111+
use core::alloc::Layout;
112+
113+
#[inline]
114+
pub fn run_ctors_once() {
115+
// No-op - native applications don't need constructor calls
116+
}
117+
118+
#[inline]
119+
pub fn maybe_link_cabi_realloc() {
120+
// No-op - native applications don't need cabi_realloc
121+
}
122+
123+
pub struct Cleanup;
124+
125+
impl Cleanup {
126+
#[inline]
127+
#[allow(clippy::new_ret_no_self)]
128+
pub fn new(layout: Layout) -> (*mut u8, Option<CleanupGuard>) {
129+
// Use the global allocator to allocate memory
130+
// SAFETY: We're allocating with a valid layout
131+
let ptr = unsafe { std::alloc::alloc(layout) };
132+
133+
// Return the pointer and a cleanup guard
134+
// If allocation fails, alloc() will panic (as per std::alloc behavior)
135+
(ptr, Some(CleanupGuard { ptr, layout }))
136+
}
137+
}
138+
139+
pub struct CleanupGuard {
140+
ptr: *mut u8,
141+
layout: Layout,
142+
}
143+
144+
impl CleanupGuard {
145+
#[inline]
146+
pub fn forget(self) {
147+
// Prevent the Drop from running
148+
core::mem::forget(self);
149+
}
150+
}
151+
152+
impl Drop for CleanupGuard {
153+
fn drop(&mut self) {
154+
// SAFETY: ptr was allocated with layout in Cleanup::new
155+
unsafe {
156+
std::alloc::dealloc(self.ptr, self.layout);
157+
}
158+
}
159+
}
160+
}
161+
}
162+
163+
// No-op export macro for native-guest mode
164+
// Native applications don't export WASM functions, so this does nothing
165+
#[allow(unused_macros)]
166+
#[macro_export]
167+
macro_rules! export {
168+
($($t:tt)*) => {
169+
// No-op: native applications don't export WASM functions
170+
};
171+
}
172+
173+
// Generated bindings follow:
174+
"""
175+
else:
176+
wrapper_content = """// Generated wrapper for WIT bindings (guest mode)
86177
//
87178
// COMPATIBILITY: wit-bindgen CLI 0.44.0 - 0.46.0
88179
// This wrapper provides a wit_bindgen::rt module compatible with the CLI-generated code.
@@ -167,9 +258,10 @@ pub mod wit_bindgen {
167258
content = wrapper_content + "\n",
168259
)
169260

170-
# Create filtered content based on mode
261+
# For native-guest mode, filter out the conflicting pub(crate) use export line
262+
# For guest mode, just concatenate directly
171263
if ctx.attr.mode == "native-guest":
172-
# Read bindgen file and filter out conflicting exports
264+
# Use Python to filter out conflicting export line
173265
filter_script = ctx.actions.declare_file(ctx.label.name + "_filter.py")
174266
filter_content = """#!/usr/bin/env python3
175267
import sys
@@ -182,11 +274,12 @@ with open(sys.argv[1], 'r') as f:
182274
with open(sys.argv[2], 'r') as f:
183275
bindgen_content = f.read()
184276
185-
# Filter out conflicting export statements for native-guest mode
277+
# Filter out conflicting pub(crate) use export line
278+
# The wrapper provides a public export! macro, so we don't need the crate-private one
186279
filtered_lines = []
187280
for line in bindgen_content.split('\\n'):
188-
# Skip lines that start with pub(crate) use __export_ and end with _impl as export;
189-
if not (line.strip().startswith('pub(crate) use __export_') and line.strip().endswith('_impl as export;')):
281+
# Skip lines that re-export as 'export' (conflicts with our macro)
282+
if not (line.strip().startswith('pub(crate) use') and line.strip().endswith(' as export;')):
190283
filtered_lines.append(line)
191284
192285
# Write combined content
@@ -206,10 +299,10 @@ with open(sys.argv[3], 'w') as f:
206299
inputs = [temp_wrapper, ctx.file.bindgen, filter_script],
207300
outputs = [out_file],
208301
mnemonic = "FilterWitWrapper",
209-
progress_message = "Filtering wrapper for {}".format(ctx.label),
302+
progress_message = "Filtering native-guest wrapper for {}".format(ctx.label),
210303
)
211304
else:
212-
# For guest mode, use file_ops component for cross-platform concatenation
305+
# Use file_ops component for cross-platform concatenation
213306
# Build JSON config for file_ops concatenate-files operation
214307
config_file = ctx.actions.declare_file(ctx.label.name + "_concat_config.json")
215308
ctx.actions.write(
@@ -397,22 +490,16 @@ def rust_wasm_component_bindgen(
397490
bindings_lib = name + "_bindings"
398491
bindings_lib_host = bindings_lib + "_host"
399492

400-
# TODO: Re-enable host bindings once we fix the export macro issue
401-
# The native-guest bindings correctly remove the export! macro (by design),
402-
# but user code unconditionally uses export!(). We need to either:
403-
# 1. Add cfg guards in user code: #[cfg(target_arch = "wasm32")]
404-
# 2. Keep the export macro in native-guest mode (but make it a no-op)
405-
# 3. Use conditional compilation in the wrapper
406-
# For now, disabled to unblock CI (WASM builds work fine)
407-
408-
# DISABLED: Create the bindings library for native platform (host) using native-guest wrapper
409-
# rust_library(
410-
# name = bindings_lib_host,
411-
# srcs = [":" + wrapper_native_guest_target],
412-
# crate_name = name.replace("-", "_") + "_bindings",
413-
# edition = "2021",
414-
# visibility = visibility, # Make native bindings publicly available
415-
# )
493+
# Create the bindings library for native platform (host) using native-guest wrapper
494+
# The native-guest wrapper includes a no-op export! macro that does nothing,
495+
# since native applications don't export WASM functions
496+
rust_library(
497+
name = bindings_lib_host,
498+
srcs = [":" + wrapper_native_guest_target],
499+
crate_name = name.replace("-", "_") + "_bindings",
500+
edition = "2021",
501+
visibility = visibility, # Make native bindings publicly available
502+
)
416503

417504
# Create a separate WASM bindings library using guest wrapper
418505
bindings_lib_wasm_base = bindings_lib + "_wasm_base"

0 commit comments

Comments
 (0)