Skip to content
Closed
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
11 changes: 7 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,9 @@ jobs:
run: |
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
':baml_language/crates/bridge_ctypes/**/*.proto' \
':baml_language/crates/bridge_ctypes/build.rs' \
':baml_language/crates/bridge_cffi/include/**' \
':baml_language/sdks/ruby/bridge_ruby/lib/baml_bridge/cffi/v1/**' \
':baml_language/sdks/swift/Sources/BamlBridge/Proto/**' \
':baml_language/sdks/swift/Sources/CBamlBridge/include/**' \
; then
Expand Down Expand Up @@ -865,7 +867,7 @@ jobs:
save-if: false
shared-key: "linux-cargo"

# bridge_ctypes/build.rs needs `protoc`; baml_go's generate.sh needs
# bridge_ctypes/build.rs uses vendored protoc; baml_go's generate.sh needs
# `protoc` + `protoc-gen-go`; bridge_typescript needs node + pnpm.
# The `cargo build -p bridge_ctypes` step uses cargo from rustup
# (not mise), so we don't need `go` itself here either.
Expand All @@ -879,9 +881,9 @@ jobs:
run: cargo fetch
working-directory: baml_language

# Rust (prost) + Python (protoc) — both driven by bridge_ctypes/build.rs.
# Writes Python pb2/pyi into baml_language/sdks/python/src/baml_bridge/cffi/v1/.
- name: "Generate Rust + Python proto bindings (bridge_ctypes)"
# Rust (prost), Python, and Ruby (protoc) — all driven by bridge_ctypes/build.rs.
# Writes Python pb2/pyi and committed Ruby clients into their SDK trees.
- name: "Generate Rust + Python + Ruby proto bindings (bridge_ctypes)"
run: cargo build -p bridge_ctypes
working-directory: baml_language

Expand Down Expand Up @@ -934,6 +936,7 @@ jobs:
baml_language/crates/bridge_ctypes
baml_language/sdks/go/baml_go/internal/cffi
baml_language/sdks/python/src/baml_bridge/cffi
baml_language/sdks/ruby/bridge_ruby/lib/baml_bridge/cffi/v1
baml_language/sdks/rust/bridge_rust/src/wire
baml_language/sdks/swift/Sources/BamlBridge/Proto
baml_language/sdks/swift/Sources/CBamlBridge/include
Expand Down
11 changes: 11 additions & 0 deletions baml_language/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions baml_language/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"sdks/cpp/sdkgen_cpp",
"sdks/csharp/sdkgen_csharp",
"sdks/go/sdkgen_go",
"sdks/ruby/sdkgen_ruby_sorbet",
"sdks/java/bridge_java",
"sdks/java/sdkgen_java",
"sdks/typescript/bridge_typescript",
Expand Down Expand Up @@ -91,6 +92,7 @@ sdkgen_java = { path = "sdks/java/sdkgen_java" }
bridge_java = { path = "sdks/java/bridge_java" }
sdkgen_swift = { path = "sdks/swift/rust/sdkgen_swift" }
sdkgen_go = { path = "sdks/go/sdkgen_go" }
sdkgen_ruby_sorbet = { path = "sdks/ruby/sdkgen_ruby_sorbet" }
sdkgen_rust = { path = "sdks/rust/sdkgen_rust" }
baml_sdkgen_types = { path = "crates/baml_sdkgen_types" }
bridge_encode = { path = "crates/bridge_encode" }
Expand Down
1 change: 1 addition & 0 deletions baml_language/crates/baml_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ sdkgen_csharp = { workspace = true }
sdkgen_go = { workspace = true }
sdkgen_java = { workspace = true }
sdkgen_python_pydantic2 = { workspace = true }
sdkgen_ruby_sorbet = { workspace = true }
sdkgen_rust = { workspace = true }
sdkgen_swift = { workspace = true }
sdkgen_typescript_shared = { workspace = true }
Expand Down
16 changes: 15 additions & 1 deletion baml_language/crates/baml_cli/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,20 @@ impl GenerateArgs {
.map(|(path, content)| (path, content.into_bytes()))
.collect()
}
OutputType::RubySorbet => {
let (files, warnings) =
sdkgen_ruby_sorbet::to_source_code_with_bytecode_and_warnings(
&pool,
&baml_bytecode,
generator.naming_convention,
Some(&embedded_baml_toml),
);
report_skipped(&reporter, "Ruby", &warnings, self.show_all_skips);
files
.into_iter()
.map(|(path, content)| (path, content.into_bytes()))
.collect()
}
OutputType::Java => sdkgen_java::to_source_code_with_bytecode_and_metadata(
&pool,
&baml_bytecode,
Expand Down Expand Up @@ -769,7 +783,7 @@ fn discover_generators(root: &Path) -> (Vec<GeneratorDef>, Vec<Diagnostic>) {
name,
"output_type",
generator.output_type.as_ref(),
r#"one of: "python/pydantic", "python/pydantic/v1", "typescript/node", "typescript/web", "swift", "go", "rust", "java", "cpp", "csharp""#,
r#"one of: "python/pydantic", "python/pydantic/v1", "typescript/node", "typescript/web", "swift", "go", "rust", "java", "ruby/sorbet", "cpp", "csharp""#,
table_range,
&mut diags,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ pub enum OutputType {
/// Java SDK (`com.boundaryml:baml-bridge` runtime).
#[strum(serialize = "java")]
Java,
/// Ruby SDK with Sorbet types (`baml-bridge` gem runtime).
#[strum(serialize = "ruby/sorbet")]
RubySorbet,
/// C++17 SDK (self-contained source tree; dlopens the shared runtime).
#[strum(serialize = "cpp")]
Cpp,
Expand All @@ -57,6 +60,7 @@ impl OutputType {
Self::Go => "go",
Self::Rust => "rust",
Self::Java => "java",
Self::RubySorbet => "ruby/sorbet",
Self::Cpp => "cpp",
Self::CSharp => "csharp",
}
Expand All @@ -70,7 +74,9 @@ impl OutputType {
/// The naming conventions this generator implements.
pub const fn naming_support(self) -> NamingSupport {
match self {
Self::Go | Self::CSharp => NamingSupport::Only(NamingConvention::Language),
Self::Go | Self::CSharp | Self::RubySorbet => {
NamingSupport::Only(NamingConvention::Language)
}
Self::Rust | Self::PythonPydantic | Self::PythonPydanticV1 => NamingSupport::Either {
default: NamingConvention::Language,
},
Expand Down Expand Up @@ -167,6 +173,7 @@ mod tests {
for output_type in [
OutputType::Go,
OutputType::CSharp,
OutputType::RubySorbet,
OutputType::Rust,
OutputType::PythonPydantic,
OutputType::PythonPydanticV1,
Expand Down
6 changes: 5 additions & 1 deletion baml_language/crates/bridge_ctypes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ cargo build -p bridge_ctypes
# -> sdks/rust/bridge_rust/src/wire/baml_bridge.cffi.v1.rs (committed)
# -> sdks/python/src/baml_bridge/cffi/v1/*_pb2.py(i)

# Ruby (protoc) — also driven by bridge_ctypes/build.rs with vendored protoc.
# The generated clients are COMMITTED so consumers do not need protoc.
# -> sdks/ruby/bridge_ruby/lib/baml_bridge/cffi/v1/*_pb.rb

# Node / TypeScript (protobufjs + napi loader)
cd sdks/typescript/bridge_typescript && pnpm build:debug
# -> sdks/typescript/bridge_typescript/typescript_src/proto/baml_cffi.{js,d.ts}
Expand Down Expand Up @@ -42,4 +46,4 @@ sdks/swift/scripts/generate-protos.sh

Other consumers (`bridge_cffi`, `bridge_wasm`, `sdks/python/rust/bridge_python`) use the Rust prost types via `bridge_ctypes` — nothing extra to regenerate. `sdks/rust/bridge_rust` is the exception: it vendors the generated file (see above) because it publishes to crates.io and must not depend on this engine-coupled crate.

No generated clients exist for Ruby, Java/Kotlin, C#/.NET, or PHP. The Java and C# bridges read and write the wire by hand (`sdks/java/baml_bridge` `ProtoReader`, `sdks/csharp/bridge_csharp` `BamlGeneratedCanonicalCodecs.cs`, and the C# generator's metadata encoder), as does the engine's trace encoder (`crates/bex_engine/src/trace_value_encode.rs`): a change to a message's fields is mirrored there by hand.
No generated clients exist for Java/Kotlin, C#/.NET, or PHP. The Java and C# bridges read and write the wire by hand (`sdks/java/baml_bridge` `ProtoReader`, `sdks/csharp/bridge_csharp` `BamlGeneratedCanonicalCodecs.cs`, and the C# generator's metadata encoder), as does the engine's trace encoder (`crates/bex_engine/src/trace_value_encode.rs`): a change to a message's fields is mirrored there by hand.
20 changes: 20 additions & 0 deletions baml_language/crates/bridge_ctypes/build.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::path::PathBuf;

mod build_support;

fn main() -> std::io::Result<()> {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=build_support.rs");

let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let proto_dir = manifest_dir.join("types");
Expand Down Expand Up @@ -72,6 +75,23 @@ fn main() -> std::io::Result<()> {
let status = cmd.status().expect("failed to run protoc");
assert!(status.success(), "protoc (python) failed with {status}");

// Generate Ruby clients into the bridge's load path. These files are
// committed so Ruby consumers do not need protoc at runtime. Remove the
// generated package first so deleted or renamed schemas cannot leave stale
// clients behind.
let ruby_out = manifest_dir.join("../../sdks/ruby/bridge_ruby/lib");
let ruby_package_out = ruby_out.join("baml_bridge/cffi/v1");
build_support::allow_missing(std::fs::remove_dir_all(&ruby_package_out))?;
std::fs::create_dir_all(&ruby_out)?;
let mut cmd = std::process::Command::new(&protoc);
cmd.arg(format!("--proto_path={}", proto_dir.display()));
cmd.arg(format!("--ruby_out={}", ruby_out.display()));
for proto in &protos {
cmd.arg(proto);
}
let status = cmd.status().expect("failed to run protoc");
assert!(status.success(), "protoc (Ruby) failed with {status}");

Ok(())
}

Expand Down
25 changes: 25 additions & 0 deletions baml_language/crates/bridge_ctypes/build_support.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
pub fn allow_missing(result: std::io::Result<()>) -> std::io::Result<()> {
match result {
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
result => result,
}
}

#[cfg(test)]
mod tests {
use super::allow_missing;
use std::io::{Error, ErrorKind};

#[test]
fn cleanup_tolerates_missing_directories_but_preserves_other_errors() {
assert!(allow_missing(Ok(())).is_ok());
assert!(allow_missing(Err(Error::from(ErrorKind::NotFound))).is_ok());
let error = allow_missing(Err(Error::new(
ErrorKind::PermissionDenied,
"cleanup refused",
)))
.unwrap_err();
assert_eq!(error.kind(), ErrorKind::PermissionDenied);
assert_eq!(error.to_string(), "cleanup refused");
}
}
4 changes: 4 additions & 0 deletions baml_language/crates/bridge_ctypes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
//! between host values and `BexExternalValue`, used by both `bridge_cffi`
//! and `bridge_wasm`.

#[cfg(test)]
#[path = "../build_support.rs"]
mod build_support;

mod error;
mod handle_table;
mod wire_depth;
Expand Down
1 change: 1 addition & 0 deletions baml_language/sdk_tests/codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ sdkgen_go = { workspace = true }
sdkgen_typescript_shared = { workspace = true }
sdkgen_java = { workspace = true }
sdkgen_rust = { workspace = true }
sdkgen_ruby_sorbet = { workspace = true }
baml_sdkgen_types = { workspace = true }
baml_base = { workspace = true }
baml_db = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions baml_language/sdk_tests/codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub mod csharp;
pub mod go;
pub mod java;
pub mod python_pydantic2;
pub mod ruby_sorbet;
pub mod rust;
pub mod swift;
pub mod typescript;
Expand Down
1 change: 1 addition & 0 deletions baml_language/sdk_tests/codegen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const GENERATORS: &[(&str, RunAll)] = &[
"python_pydantic2",
sdk_test_codegen::python_pydantic2::run_all,
),
("ruby_sorbet", sdk_test_codegen::ruby_sorbet::run_all),
("rust", sdk_test_codegen::rust::run_all),
("swift", sdk_test_codegen::swift::run_all),
("typescript", sdk_test_codegen::typescript::run_all),
Expand Down
29 changes: 29 additions & 0 deletions baml_language/sdk_tests/codegen/src/ruby_sorbet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//! Stage the Ruby SDK fixtures.
use std::fs;

use baml_sdkgen_types::NamingConvention;
use sdk_test_harness_runner::fixtures;

use crate::{CodegenCtx, Overlay, load_fixture, write_codegen_output};

pub fn run_all(ctx: &CodegenCtx) {
for fixture in fixtures::checked_shared(&ctx.fixtures_root) {
let loaded = load_fixture(&ctx.fixtures_root, fixture);
let (files, skipped) = sdkgen_ruby_sorbet::to_source_code_with_bytecode_and_skipped(
&loaded.pool,
&loaded.baml_bytecode,
NamingConvention::Language,
None,
);
eprintln!("Ruby {fixture} skipped: {}", skipped.join(", "));
let root = ctx.crate_dir.join(fixture);
let generated = root.join("generated");
if generated.exists() {
fs::remove_dir_all(&generated).unwrap();
}
write_codegen_output(&generated, files, fixture);
let mut overlay = Overlay::new(&root);
overlay.copy_tree(&root.join("customizable"), "");
overlay.install();
}
}
Loading