cxx icon indicating copy to clipboard operation
cxx copied to clipboard

Symbol mismatch with cxxbridge-cmd in Bazel

Open sayrer opened this issue 1 month ago • 1 comments

In my build, the linker was looking for symbols like _twitter_text$cxxbridge1$0$autolink_all but the Rust static library contained symbols like _twitter_text$cxxbridge1$189$autolink_all. This happened because:

  1. The cxxbridge-cmd tool generates C++ bridge code with version $0$ when processing standalone .rs files
  2. The Rust compiler embeds the actual cxx crate version (1.0.189, hence $189$) into symbols when building the library

sayrer avatar Nov 30 '25 19:11 sayrer

How do you build cxxbridge-cmd? Is your build environment correctly emulating Cargo when building cxxbridge-cmd? In particular does your build environment set CARGO_PKG_VERSION_PATCH when building cxxbridge-cmd?

Since https://github.com/dtolnay/cxx/pull/1665 the symbols generated by cxx include the value of CARGO_PKG_VERSION_PATCH - see https://github.com/dtolnay/cxx/blob/95eef18e72fa27aad1e5d88468d4b72167d8e0cc/syntax/mangle.rs#L78

anforowicz avatar Dec 02 '25 16:12 anforowicz

I got the same issue.

In my project, I have:

[dependencies]
cxx = { version = "1.0", default-features = false, features = ["alloc"] }

and in the build.rs of my project, I invoked the cxxbridge command, which is installed by cargo install, and the generated cpp file contains the $190 part (CARGO_PKG_VERSION_PATCH).

Then I get the error:

rust-lld: error: undefined symbol: cxxbridge1$coverage_new

If I change to specify the specific patch version

[dependencies]
cxx = { version = "1.0.190", default-features = false, features = ["alloc"] }

It seems to work without error.

However, this is not a good user experience! Probably should write somewhere in the documentation that one shold specify the patch version when using cxx as a dependency.

am009 avatar Dec 03 '25 04:12 am009

I got the same issue.

It seems that in your case the build is managed by cargo rather than by Bazel, so it seems to be a separate issue. Can we therefore move this discussion to a separate issue please?

in the build.rs of my project, I invoked the cxxbridge command, which is installed by cargo install

Does your issue go away if you avoid depending on cxxbridge that is installed system-wide (potentially at a different version than the one resolved by cargo from Cargo.toml)? https://cxx.rs/build/cargo.html describes how this can be done.

However, this is not a good user experience!

Yeah, we can together try to improve the experience. At the same time note that the error you are seeing exposes a pre-existing problem/risk - you get undefined behavior if one side of FFI uses a version of cxx that expects different ABI that the other side of FFI (if the other side uses a different cxx version). For example - the exact parameters of thunks generated by cxx are an internal implementation detail that can change in any patch-version-update. Therefore keeping both sides of the FFI boundary in-sync is important to prevent undefined behavior.

anforowicz avatar Dec 03 '25 17:12 anforowicz

Thanks for the reply, I understand now. It is indeed necessary to ensure consistency between the two versions to prevent undefined behavior.

am009 avatar Dec 04 '25 01:12 am009

How do you build cxxbridge-cmd?

In MODULE.bazel

#
# The codegen tool needed by cxx.
#
http_archive(
    name = "cxxbridge-cmd",
    build_file_content = """
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("@cxxbridge_cmd_deps//:defs.bzl", "aliases", "all_crate_deps")

rust_binary(
    name = "cxxbridge-cmd",
    srcs = glob(["src/**/*.rs"]),
    aliases = aliases(),
    compile_data = glob(["src/gen/**/*.h"]),
    edition = "2021",
    visibility = ["//visibility:public"],
    deps = all_crate_deps(
        normal = True,
    ),
)
    """,
    sha256 = "6a368ed4a0fd83ebd3f2808613842d942a409c41cc24cd9d83f1696a00d78afe",
    strip_prefix = "cxxbridge-cmd-1.0.189",
    type = "tar.gz",
    urls = ["https://static.crates.io/crates/cxxbridge-cmd/cxxbridge-cmd-1.0.189.crate"],
)

cxxbridge_cmd_deps = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate")
cxxbridge_cmd_deps.splicing_config(
    repositories = ["cxxbridge_cmd_deps"],
    resolver_version = "2",
)
cxxbridge_cmd_deps.from_cargo(
    name = "cxxbridge_cmd_deps",
    cargo_lockfile = "//3rdparty/cxxbridge:Cargo.lock",
    manifests = ["@cxxbridge-cmd//:Cargo.toml"],
)
use_repo(
    cxxbridge_cmd_deps,
    "cxxbridge_cmd_deps",
)

I have a workaround for it, but it's pretty gross.

genrule(
    name = "twitter_text_h",
    srcs = [
        "//rust/twitter-text:twitter_text_ffi",
    ],
    outs = ["twitter-text.h"],
    cmd = "$(location @cxxbridge-cmd//:cxxbridge-cmd) --header $(location //rust/twitter-text:twitter_text_ffi) | sed 's/\\$$cxxbridge1\\$$0\\$$/\\$$cxxbridge1\\$$190\\$$/g' > $(location twitter-text.h)",
    tools = ["@cxxbridge-cmd//:cxxbridge-cmd"],
)

sayrer avatar Dec 04 '25 18:12 sayrer

RE: https://github.com/dtolnay/cxx/issues/1676#issuecomment-3613784904

I think you need to also provide version in your rust_binary rule.

anforowicz avatar Dec 04 '25 19:12 anforowicz

OK, this works now. Maybe the docs use this as an example? It's pretty sparse right now.

#
# The codegen tool needed by cxx.
#

CXX_BRIDGE_VERSION = "1.0.190"

http_archive(
    name = "cxxbridge-cmd",
    build_file_content = """
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("@cxxbridge_cmd_deps//:defs.bzl", "aliases", "all_crate_deps")

rust_binary(
    name = "cxxbridge-cmd",
    srcs = glob(["src/**/*.rs"]),
    aliases = aliases(),
    compile_data = glob(["src/gen/**/*.h"]),
    edition = "2021",
    visibility = ["//visibility:public"],
    deps = all_crate_deps(
        normal = True,
    ),
    version = "%s",
)
    """ % CXX_BRIDGE_VERSION,
    sha256 = "b1f29a879d35f7906e3c9b77d7a1005a6a0787d330c09dfe4ffb5f617728cb44",
    strip_prefix = "cxxbridge-cmd-" + CXX_BRIDGE_VERSION,
    type = "tar.gz",
    urls = ["https://static.crates.io/crates/cxxbridge-cmd/cxxbridge-cmd-" + CXX_BRIDGE_VERSION + ".crate"],
)

cxxbridge_cmd_deps = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate")
cxxbridge_cmd_deps.splicing_config(
    repositories = ["cxxbridge_cmd_deps"],
    resolver_version = "2",
)
cxxbridge_cmd_deps.from_cargo(
    name = "cxxbridge_cmd_deps",
    cargo_lockfile = "//3rdparty/cxxbridge:Cargo.lock",
    manifests = ["@cxxbridge-cmd//:Cargo.toml"],
)
use_repo(
    cxxbridge_cmd_deps,
    "cxxbridge_cmd_deps",
)

sayrer avatar Dec 04 '25 19:12 sayrer

OK, this works now.

Thanks. I guess this issue can be marked as closed now.

Maybe the docs use this as an example? It's pretty sparse right now.

Maybe https://github.com/dtolnay/cxx/pull/1679 will help a little (it highlights how the same version is used across cxxbridge, cxxbridge-macro, and cxx targets), but I don't use Bazel often myself, so I hope that if there are other doc improvements possible, then Bazel experts can chime in with PRs of their own.

anforowicz avatar Dec 04 '25 20:12 anforowicz