Skip to main content

gen_bazel/
lib.rs

1//! `gen-bazel` — Bazel renderer for the gen engine.
2//!
3//! Same typed Manifest as gen-nix / gen-nix-bulk; emits a typed
4//! Starlark AST → canonical pretty-printer → BUILD.bazel +
5//! MODULE.bazel files. The destination per the GEN.md plan:
6//! one source-of-truth Manifest, N typed renderers (Nix / Bazel /
7//! Buck / Bazel-rust-rules), operators pick the backend via shikumi.
8//!
9//! The Starlark AST is intentionally minimal (atoms / lists / dicts /
10//! function-calls / assignments) — matches what BUILD files actually
11//! use; full Starlark eval is not in scope.
12
13pub mod ast;
14pub mod render;
15
16pub use ast::{StarlarkValue, StarlarkStmt, KwArg};
17pub use render::render_file;
18
19use gen_types::{Manifest, Package};
20
21/// Render a typed Manifest to a BUILD.bazel file body. One
22/// `rust_library` or `rust_binary` call per workspace member, plus
23/// `rust_test` for the integration tests when present.
24#[must_use]
25pub fn render_build_bazel(manifest: &Manifest) -> String {
26    let mut stmts: Vec<StarlarkStmt> = Vec::new();
27    stmts.push(StarlarkStmt::Load {
28        module: "@rules_rust//rust:defs.bzl".to_string(),
29        symbols: vec!["rust_library".into(), "rust_binary".into()],
30    });
31    for pkg in &manifest.packages {
32        stmts.push(StarlarkStmt::Call {
33            func: "rust_library".to_string(),
34            args: rust_library_args(pkg),
35        });
36    }
37    render::render_file(&stmts)
38}
39
40/// Render a MODULE.bazel that declares the cargo dependencies via
41/// the `cargo` extension. Each external crate becomes one
42/// `crate.from_cargo` entry; operators use `bazel mod tidy` after to
43/// pin transitives.
44#[must_use]
45pub fn render_module_bazel(manifest: &Manifest) -> String {
46    let mut stmts: Vec<StarlarkStmt> = Vec::new();
47    let module_name = manifest
48        .packages
49        .first()
50        .map(|p| p.name.clone())
51        .unwrap_or_else(|| "workspace".to_string());
52    let module_version = manifest
53        .packages
54        .first()
55        .map(|p| p.version.to_string())
56        .unwrap_or_else(|| "0.1.0".to_string());
57    stmts.push(StarlarkStmt::Call {
58        func: "module".to_string(),
59        args: vec![
60            KwArg::str("name", &module_name),
61            KwArg::str("version", &module_version),
62        ],
63    });
64    stmts.push(StarlarkStmt::Assign {
65        name: "rust".to_string(),
66        value: StarlarkValue::Call {
67            func: "use_extension".to_string(),
68            args: vec![
69                KwArg::positional(StarlarkValue::str("@rules_rust//rust:extensions.bzl")),
70                KwArg::positional(StarlarkValue::str("rust")),
71            ],
72        },
73    });
74    stmts.push(StarlarkStmt::Call {
75        func: "use_repo".to_string(),
76        args: vec![
77            KwArg::positional(StarlarkValue::Ident("rust".to_string())),
78            KwArg::positional(StarlarkValue::str("rust_toolchains")),
79        ],
80    });
81    render::render_file(&stmts)
82}
83
84fn rust_library_args(pkg: &Package) -> Vec<KwArg> {
85    vec![
86        KwArg::str("name", &pkg.name),
87        KwArg::positional_named(
88            "srcs",
89            StarlarkValue::Call {
90                func: "glob".to_string(),
91                args: vec![KwArg::positional(StarlarkValue::List(vec![
92                    StarlarkValue::str("src/**/*.rs"),
93                ]))],
94            },
95        ),
96        KwArg::str("edition", "2021"),
97        KwArg::positional_named(
98            "deps",
99            StarlarkValue::List(
100                pkg.dependencies
101                    .iter()
102                    .map(|d| StarlarkValue::str(format!("@crate_index//:{}", d.name)))
103                    .collect(),
104            ),
105        ),
106        KwArg::positional_named(
107            "visibility",
108            StarlarkValue::List(vec![StarlarkValue::str("//visibility:public")]),
109        ),
110    ]
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use gen_types::*;
117
118    fn demo() -> Manifest {
119        let p = Package {
120            name: "demo".into(),
121            version: Version::new(0, 1, 0),
122            source: PackageSource::Path { path: ".".into() },
123            registry: Registry::CratesIo,
124            dependencies: vec![Dependency {
125                name: "serde".into(),
126                constraint: VersionConstraint::from_spec(ConstraintSpec::Caret(Version::new(1, 0, 0))),
127                kind: DependencyKind::Direct,
128                features_enabled: vec![],
129                default_features: true,
130                target_predicate: None,
131                source_override: None,
132            }],
133            features: vec![],
134            build_steps: vec![],
135            license: None,
136            description: None,
137            authors: vec![],
138            homepage: None,
139            repository: None,
140        };
141        Manifest::new("/x", Workspace::single_package("/x", "cargo"), vec![p], None)
142    }
143
144    #[test]
145    fn renders_build_bazel_with_rust_library() {
146        let s = render_build_bazel(&demo());
147        assert!(s.contains("load(\"@rules_rust//rust:defs.bzl\""));
148        assert!(s.contains("rust_library("));
149        assert!(s.contains("name = \"demo\""));
150        assert!(s.contains("@crate_index//:serde"));
151    }
152
153    #[test]
154    fn renders_module_bazel_with_module_call_and_use_extension() {
155        let s = render_module_bazel(&demo());
156        assert!(s.contains("module(name = \"demo\""));
157        assert!(s.contains("rust = use_extension("));
158        assert!(s.contains("use_repo(rust"));
159    }
160
161    #[test]
162    fn renderer_is_deterministic() {
163        let m = demo();
164        let a = render_build_bazel(&m);
165        let b = render_build_bazel(&m);
166        assert_eq!(a, b);
167    }
168}