Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ bazel_dep(name = "rules_foreign_cc", version = "0.15.0")
crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate")
crate.from_cargo(
name = "crate_index",
lockfile = "//:Cargo.Bazel.lock",
cargo_lockfile = "//:Cargo.lock",
lockfile = "//:Cargo.Bazel.lock",
manifests = [
"//:Cargo.toml",
],
Expand All @@ -52,7 +52,7 @@ crubit_toolchains.configure(
llvm_version = "22.1.0",
rust_version = "nightly/2026-07-01",
)
use_repo(crubit_toolchains, "llvm_toolchain", "rust_toolchains", "llvm_version_check")
use_repo(crubit_toolchains, "llvm_toolchain", "llvm_version_check", "rust_toolchains")

register_toolchains(
"@rust_toolchains//:all",
Expand Down
1 change: 1 addition & 0 deletions cc_bindings_from_rs/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ crubit_rust_binary(
"not_run",
"notap",
],
visibility = ["//visibility:public"],
deps = [
":cmdline",
":cpp_api_from_rust_lib",
Expand Down
2 changes: 1 addition & 1 deletion cc_bindings_from_rs/cc_bindings_from_rs_sh_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function delete_all_test_outputs() {
rm -rf "$STDERR_PATH" "$STDOUT_PATH" "$H_OUT_PATH" "$RS_OUT_PATH" "$TARGET_JSON_PATH"
}

readonly CC_BINDINGS_FROM_RS_PATH="${RUNFILES}/cc_bindings_from_rs/cc_bindings_from_rs"
readonly CC_BINDINGS_FROM_RS_PATH="${RUNFILES}/rules_crubit/cc_bindings_from_rs/cc_bindings_from_rs"
readonly SYSROOT_PATH="${RUNFILES}/${G3_SYSROOT_PATH}"
readonly DEFAULT_CLANG_FORMAT_EXE_PATH="${CRUBIT_CLANG_FORMAT_EXE_PATH}"

Expand Down
1 change: 1 addition & 0 deletions crubit_explorer/.bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import %workspace%/../.bazelrc
1 change: 1 addition & 0 deletions crubit_explorer/.bazelversion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9.2.0
56 changes: 38 additions & 18 deletions crubit_explorer/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

load(
"@rules_crubit//common:crubit_wrapper_macros_oss.bzl",
"crubit_rust_binary",
"crubit_rust_test",
)
load("@rules_license//rules:license.bzl", "license")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")

package(
default_applicable_licenses = [":license"],
Expand All @@ -15,35 +19,51 @@ license(
package_name = "crubit_explorer",
)

rust_binary(
crubit_rust_binary(
name = "server",
srcs = ["src/main.rs"],
data = [
"@rules_crubit//cc_bindings_from_rs",
"@rules_rust//rust/toolchain:current_rustc_files",
"@rules_rust//rust/toolchain:current_rustc_lib_files",
],
edition = "2024",
env = {
"RUSTC_RUNFILES_PATH": "$(rlocationpath @rules_rust//rust/toolchain:current_rustc_files)",
},
visibility = ["//visibility:public"],
deps = [
"@crate_index//:axum", # v0_8
"@crate_index//:base64", # v0_22
"@crate_index//:serde", # v1
"@crate_index//:serde_json", # v1
"@crate_index//:tempfile", # v3
"@crate_index//:tokio", # v1
"@crate_index//:tower-http",
"@crubit_explorer_crates//:axum",
"@crubit_explorer_crates//:base64",
"@crubit_explorer_crates//:serde",
"@crubit_explorer_crates//:serde_json",
"@crubit_explorer_crates//:tempfile",
"@crubit_explorer_crates//:tokio",
"@rules_rust//tools/runfiles",
],
)

rust_test(
crubit_rust_test(
name = "server_test",
size = "small",
srcs = ["src/main.rs"],
data = [
"@rules_crubit//cc_bindings_from_rs",
"@rules_rust//rust/toolchain:current_rustc_files",
"@rules_rust//rust/toolchain:current_rustc_lib_files",
],
edition = "2024",
env = {
"RUSTC_RUNFILES_PATH": "$(rlocationpath @rules_rust//rust/toolchain:current_rustc_files)",
},
deps = [
"@crate_index//:axum", # v0_8
"@crate_index//:base64", # v0_22
"@crate_index//:serde", # v1
"@crate_index//:serde_json", # v1
"@crate_index//:tempfile", # v3
"@crate_index//:tokio", # v1
"@crate_index//:tower", # v0_5
"@crate_index//:tower-http",
"@crubit_explorer_crates//:axum",
"@crubit_explorer_crates//:base64",
"@crubit_explorer_crates//:serde",
"@crubit_explorer_crates//:serde_json",
"@crubit_explorer_crates//:tempfile",
"@crubit_explorer_crates//:tokio",
"@crubit_explorer_crates//:tower",
"@rules_rust//tools/runfiles",
],
)
1 change: 0 additions & 1 deletion crubit_explorer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ serde_json = "1.0.150"
tempfile = "3.27.0"
tokio = { version = "1.52.3", features = ["full"] }
tower = { version = "0.5.2", features = ["util"] }
tower-http = { version = "0.7.0", features = ["fs"] }

[workspace]

26 changes: 19 additions & 7 deletions crubit_explorer/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,32 @@ module(

bazel_dep(name = "rules_rust", version = "0.71.3")
bazel_dep(name = "rules_license", version = "1.0.0")
bazel_dep(name = "rules_crubit")
local_path_override(
module_name = "rules_crubit",
path = "..",
)

rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(
edition = "2024",
crubit_toolchains = use_extension("@rules_crubit//bazel:extensions.bzl", "crubit_toolchains")
crubit_toolchains.configure(
llvm_version = "22.1.0",
rust_version = "nightly/2026-07-01",
)
use_repo(rust, "rust_toolchains")

register_toolchains("@rust_toolchains//:all")
register_toolchains("@rust_toolchains//:all", "@llvm_toolchain//:all")

use_repo(
crubit_toolchains,
"llvm_toolchain",
"llvm_version_check",
"rust_toolchains",
)

# Crate Universe to import dependencies from Cargo.toml
crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate")
crate.from_cargo(
name = "crate_index",
name = "crubit_explorer_crates",
cargo_lockfile = "//:Cargo.lock",
manifests = ["//:Cargo.toml"],
)
use_repo(crate, "crate_index")
use_repo(crate, "crubit_explorer_crates")
149 changes: 149 additions & 0 deletions crubit_explorer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,117 @@

use axum::{routing, Router};
use std::net::SocketAddr;
use std::path::PathBuf;
use tokio::net::TcpListener;

use runfiles::Runfiles;

const CC_BINDINGS_FROM_RS_RLOCATION: &str =
"rules_crubit/cc_bindings_from_rs/cc_bindings_from_rs";

fn get_cc_bindings_from_rs_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
// Environment variable for location to cc_bindings_from_rs binary
if let Ok(env_path) = std::env::var("CC_BINDINGS_FROM_RS")
&& let path = PathBuf::from(env_path)
&& path.exists()
{
return Ok(path);
}

// Check bazel runfiles tree
if let Ok(r) = Runfiles::create()
&& let Some(path) = runfiles::rlocation!(r, CC_BINDINGS_FROM_RS_RLOCATION)
&& path.exists()
{
return Ok(path);
}

// Check if cc_bindings_from_rs is in the same directory as the executable
// This is useful when crubit_explorer is run in a tarball or Docker container with
// a specific directory structure
if let Ok(mut exe_path) = std::env::current_exe() {
exe_path.pop(); // Remove the executable name, leaving the directory
let adjacent_path = exe_path.join("cc_bindings_from_rs");
if adjacent_path.exists() {
return Ok(adjacent_path);
}
}

// Check system PATH
if let Some(path) = find_in_path("cc_bindings_from_rs") {
return Ok(path);
}

Err("cc_bindings_from_rs binary not found via CC_BINDINGS_FROM_RS env var, Bazel runfiles, adjacent to executable, or in system PATH".into())
}

fn new_cc_bindings_from_rs_command() -> Result<std::process::Command, Box<dyn std::error::Error>> {
let binary_path = get_cc_bindings_from_rs_path()?;
let mut cmd = std::process::Command::new(binary_path);

let mut extra_lib_dirs = Vec::new();

// Check Bazel runfiles
if let Ok(r) = Runfiles::create()
&& let Ok(rustc_runfiles_env) = std::env::var("RUSTC_RUNFILES_PATH")
&& let Some(rustc_path) = runfiles::rlocation!(r, &rustc_runfiles_env)
{
let mut lib_dir = rustc_path;
lib_dir.pop(); // pop rustc
lib_dir.pop(); // pop bin
let lib_dir = lib_dir.join("lib");
if lib_dir.exists() {
extra_lib_dirs.push(lib_dir);
}
}

// Check adjacent lib directory to the current executable (useful in Docker/production tarball)
if let Ok(mut exe_path) = std::env::current_exe() {
exe_path.pop();
let adjacent_lib = exe_path.join("lib");
if adjacent_lib.exists() {
extra_lib_dirs.push(adjacent_lib);
}
}

if !extra_lib_dirs.is_empty() {
const LIB_PATH_ENV: &str = cfg_select! {
target_os = "macos" => "DYLD_LIBRARY_PATH",
target_os = "windows" => "PATH",
_ => "LD_LIBRARY_PATH",
};

let mut paths = extra_lib_dirs;
if let Some(old_val) = std::env::var_os(LIB_PATH_ENV) {
paths.extend(std::env::split_paths(&old_val));
}

let new_val = std::env::join_paths(paths)?;
cmd.env(LIB_PATH_ENV, new_val);
}

Ok(cmd)
}

fn find_in_path(name: &str) -> Option<PathBuf> {
let paths = std::env::var_os("PATH")?;
std::env::split_paths(&paths).find_map(|dir| {
let full_path = dir.join(name);
full_path.exists().then_some(full_path)
})
}

fn app() -> Router {
Router::new().route("/", routing::get(|| async { "Hello, World!" }))
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
match get_cc_bindings_from_rs_path() {
Ok(path) => println!("cc_bindings_from_rs found at: {}", path.display()),
Err(err) => eprintln!("Error locating cc_bindings_from_rs: {}", err),
}

let app = app();

let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
Expand Down Expand Up @@ -43,4 +146,50 @@ mod tests {
let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap();
assert_eq!(&body[..], b"Hello, World!");
}

#[tokio::test]
async fn test_cc_bindings_from_rs_help() {
let mut cmd = new_cc_bindings_from_rs_command()
.expect("Failed to create cc_bindings_from_rs command");
let output = cmd.arg("--help").output().expect("Failed to execute cc_bindings_from_rs");

assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("Generates C++ bindings for a Rust crate"));
}

#[tokio::test]
async fn test_generate_bindings() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let rs_input = temp_dir.path().join("input.rs");
let h_out = temp_dir.path().join("output.h");
let rs_out = temp_dir.path().join("output.rs");

std::fs::write(&rs_input, b"#[no_mangle] pub extern \"C\" fn foo() {}")
.expect("Failed to write input file");

let mut cmd = new_cc_bindings_from_rs_command()
.expect("Failed to create cc_bindings_from_rs command");
cmd.arg(format!("--h-out={}", h_out.display()))
.arg(format!("--rs-out={}", rs_out.display()))
.arg("--crubit-support-path-format=<crubit/support/{header}>")
.arg("--")
.arg(&rs_input)
.arg("--crate-type=lib");

let output = cmd.output().expect("Failed to execute cc_bindings_from_rs");

let stderr = String::from_utf8_lossy(&output.stderr);

assert!(output.status.success(), "Command failed with stderr: {}", stderr);
assert!(h_out.exists());
assert!(rs_out.exists());

let h_content = std::fs::read_to_string(&h_out).expect("Failed to read h_out");
let rs_content = std::fs::read_to_string(&rs_out).expect("Failed to read rs_out");

assert!(!h_content.is_empty());
assert!(!rs_content.is_empty());
assert!(h_content.contains("foo"));
}
}
Empty file added patches/BUILD
Empty file.
Loading