blob: c4cc24952df96318cd39c57d41e304a62c8958a8 [file] [log] [blame]
Brian Silvermancc09f182022-03-09 15:40:20 -08001//! Tools for rendering and writing BUILD and other Starlark files
2
3mod template_engine;
4
5use std::collections::BTreeMap;
6use std::fs;
7use std::path::{Path, PathBuf};
8use std::str::FromStr;
9
10use anyhow::{bail, Context as AnyhowContext, Result};
11
12use crate::config::RenderConfig;
13use crate::context::Context;
14use crate::rendering::template_engine::TemplateEngine;
15use crate::splicing::default_splicing_package_crate_id;
16use crate::utils::starlark::Label;
17
18pub struct Renderer {
19 config: RenderConfig,
20 engine: TemplateEngine,
21}
22
23impl Renderer {
24 pub fn new(config: RenderConfig) -> Self {
25 let engine = TemplateEngine::new(&config);
26 Self { config, engine }
27 }
28
29 pub fn render(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
30 let mut output = BTreeMap::new();
31
32 output.extend(self.render_build_files(context)?);
33 output.extend(self.render_crates_module(context)?);
34
35 if let Some(vendor_mode) = &self.config.vendor_mode {
36 match vendor_mode {
37 crate::config::VendorMode::Local => {
38 // Nothing to do for local vendor crate
39 }
40 crate::config::VendorMode::Remote => {
41 output.extend(self.render_vendor_support_files(context)?);
42 }
43 }
44 }
45
46 Ok(output)
47 }
48
49 fn render_crates_module(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
50 let module_label = render_module_label(&self.config.crates_module_template, "defs.bzl")
51 .context("Failed to resolve string to module file label")?;
52 let module_build_label =
53 render_module_label(&self.config.crates_module_template, "BUILD.bazel")
54 .context("Failed to resolve string to module file label")?;
55
56 let mut map = BTreeMap::new();
57 map.insert(
58 Renderer::label_to_path(&module_label),
59 self.engine.render_module_bzl(context)?,
60 );
61 map.insert(
62 Renderer::label_to_path(&module_build_label),
63 self.engine.render_module_build_file(context)?,
64 );
65
66 Ok(map)
67 }
68
69 fn render_build_files(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
70 let default_splicing_package_id = default_splicing_package_crate_id();
71 self.engine
72 .render_crate_build_files(context)?
73 .into_iter()
74 // Do not render the default splicing package
75 .filter(|(id, _)| *id != &default_splicing_package_id)
76 // Do not render local packages
77 .filter(|(id, _)| !context.workspace_members.contains_key(id))
78 .map(|(id, content)| {
79 let ctx = &context.crates[id];
80 let label = match render_build_file_template(
81 &self.config.build_file_template,
82 &ctx.name,
83 &ctx.version,
84 ) {
85 Ok(label) => label,
86 Err(e) => bail!(e),
87 };
88
89 let filename = Renderer::label_to_path(&label);
90
91 Ok((filename, content))
92 })
93 .collect()
94 }
95
96 fn render_vendor_support_files(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
97 let module_label = render_module_label(&self.config.crates_module_template, "crates.bzl")
98 .context("Failed to resolve string to module file label")?;
99
100 let mut map = BTreeMap::new();
101 map.insert(
102 Renderer::label_to_path(&module_label),
103 self.engine.render_vendor_module_file(context)?,
104 );
105
106 Ok(map)
107 }
108
109 fn label_to_path(label: &Label) -> PathBuf {
110 match &label.package {
111 Some(package) => PathBuf::from(format!("{}/{}", package, label.target)),
112 None => PathBuf::from(&label.target),
113 }
114 }
115}
116
117/// Write a set of [CrateContext][crate::context::CrateContext] to disk.
118pub fn write_outputs(
119 outputs: BTreeMap<PathBuf, String>,
120 out_dir: &Path,
121 dry_run: bool,
122) -> Result<()> {
123 let outputs: BTreeMap<PathBuf, String> = outputs
124 .into_iter()
125 .map(|(path, content)| (out_dir.join(path), content))
126 .collect();
127
128 if dry_run {
129 for (path, content) in outputs {
130 println!(
131 "==============================================================================="
132 );
133 println!("{}", path.display());
134 println!(
135 "==============================================================================="
136 );
137 println!("{}\n", content);
138 }
139 } else {
140 for (path, content) in outputs {
141 // Ensure the output directory exists
142 fs::create_dir_all(
143 path.parent()
144 .expect("All file paths should have valid directories"),
145 )?;
146
147 fs::write(&path, content.as_bytes())
148 .context(format!("Failed to write file to disk: {}", path.display()))?;
149 }
150 }
151
152 Ok(())
153}
154
155/// Render the Bazel label of a crate
156pub fn render_crate_bazel_label(
157 template: &str,
158 repository_name: &str,
159 name: &str,
160 version: &str,
161 target: &str,
162) -> String {
163 template
164 .replace("{repository}", repository_name)
165 .replace("{name}", name)
166 .replace("{version}", version)
167 .replace("{target}", target)
168}
169
170/// Render the Bazel label of a crate
171pub fn render_crate_bazel_repository(
172 template: &str,
173 repository_name: &str,
174 name: &str,
175 version: &str,
176) -> String {
177 template
178 .replace("{repository}", repository_name)
179 .replace("{name}", name)
180 .replace("{version}", version)
181}
182
183/// Render the Bazel label of a crate
184pub fn render_crate_build_file(template: &str, name: &str, version: &str) -> String {
185 template
186 .replace("{name}", name)
187 .replace("{version}", version)
188}
189
190/// Render the Bazel label of a vendor module label
191pub fn render_module_label(template: &str, name: &str) -> Result<Label> {
192 Label::from_str(&template.replace("{file}", name))
193}
194
195/// Render the Bazel label of a platform triple
196pub fn render_platform_constraint_label(template: &str, triple: &str) -> String {
197 template.replace("{triple}", triple)
198}
199
200fn render_build_file_template(template: &str, name: &str, version: &str) -> Result<Label> {
201 Label::from_str(
202 &template
203 .replace("{name}", name)
204 .replace("{version}", version),
205 )
206}
207
208#[cfg(test)]
209mod test {
210 use super::*;
211
212 use crate::config::{Config, CrateId, VendorMode};
213 use crate::context::crate_context::{CrateContext, Rule};
Brian Silverman5f6f2762022-08-13 19:30:05 -0700214 use crate::context::{BuildScriptAttributes, CommonAttributes, Context, TargetAttributes};
Brian Silvermancc09f182022-03-09 15:40:20 -0800215 use crate::metadata::Annotations;
216 use crate::test;
217
218 fn mock_render_config() -> RenderConfig {
219 serde_json::from_value(serde_json::json!({
Brian Silverman5f6f2762022-08-13 19:30:05 -0700220 "repository_name": "test_rendering",
221 "regen_command": "cargo_bazel_regen_command",
Brian Silvermancc09f182022-03-09 15:40:20 -0800222 }))
223 .unwrap()
224 }
225
226 fn mock_target_attributes() -> TargetAttributes {
227 TargetAttributes {
228 crate_name: "mock_crate".to_owned(),
229 crate_root: Some("src/root.rs".to_owned()),
230 ..TargetAttributes::default()
231 }
232 }
233
234 #[test]
235 fn render_rust_library() {
236 let mut context = Context::default();
237 let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
238 context.crates.insert(
239 crate_id.clone(),
240 CrateContext {
241 name: crate_id.name,
242 version: crate_id.version,
243 targets: vec![Rule::Library(mock_target_attributes())],
244 ..CrateContext::default()
245 },
246 );
247
248 let renderer = Renderer::new(mock_render_config());
249 let output = renderer.render(&context).unwrap();
250
251 let build_file_content = output
252 .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
253 .unwrap();
254
255 assert!(build_file_content.contains("rust_library("));
256 assert!(build_file_content.contains("name = \"mock_crate\""));
257 }
258
259 #[test]
260 fn render_cargo_build_script() {
261 let mut context = Context::default();
262 let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
263 context.crates.insert(
264 crate_id.clone(),
265 CrateContext {
266 name: crate_id.name,
267 version: crate_id.version,
268 targets: vec![Rule::BuildScript(TargetAttributes {
269 crate_name: "build_script_build".to_owned(),
270 crate_root: Some("build.rs".to_owned()),
271 ..TargetAttributes::default()
272 })],
273 // Build script attributes are required.
274 build_script_attrs: Some(BuildScriptAttributes::default()),
275 ..CrateContext::default()
276 },
277 );
278
279 let renderer = Renderer::new(mock_render_config());
280 let output = renderer.render(&context).unwrap();
281
282 let build_file_content = output
283 .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
284 .unwrap();
285
286 assert!(build_file_content.contains("cargo_build_script("));
287 assert!(build_file_content.contains("name = \"build_script_build\""));
288
289 // Ensure `cargo_build_script` requirements are met
290 assert!(build_file_content.contains("name = \"mock_crate_build_script\""));
291 }
292
293 #[test]
294 fn render_proc_macro() {
295 let mut context = Context::default();
296 let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
297 context.crates.insert(
298 crate_id.clone(),
299 CrateContext {
300 name: crate_id.name,
301 version: crate_id.version,
302 targets: vec![Rule::ProcMacro(mock_target_attributes())],
303 ..CrateContext::default()
304 },
305 );
306
307 let renderer = Renderer::new(mock_render_config());
308 let output = renderer.render(&context).unwrap();
309
310 let build_file_content = output
311 .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
312 .unwrap();
313
314 assert!(build_file_content.contains("rust_proc_macro("));
315 assert!(build_file_content.contains("name = \"mock_crate\""));
316 }
317
318 #[test]
319 fn render_binary() {
320 let mut context = Context::default();
321 let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
322 context.crates.insert(
323 crate_id.clone(),
324 CrateContext {
325 name: crate_id.name,
326 version: crate_id.version,
327 targets: vec![Rule::Binary(mock_target_attributes())],
328 ..CrateContext::default()
329 },
330 );
331
332 let renderer = Renderer::new(mock_render_config());
333 let output = renderer.render(&context).unwrap();
334
335 let build_file_content = output
336 .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
337 .unwrap();
338
339 assert!(build_file_content.contains("rust_binary("));
340 assert!(build_file_content.contains("name = \"mock_crate__bin\""));
341 }
342
343 #[test]
344 fn render_additive_build_contents() {
345 let mut context = Context::default();
346 let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
347 context.crates.insert(
348 crate_id.clone(),
349 CrateContext {
350 name: crate_id.name,
351 version: crate_id.version,
352 targets: vec![Rule::Binary(mock_target_attributes())],
353 additive_build_file_content: Some(
354 "# Hello World from additive section!".to_owned(),
355 ),
356 ..CrateContext::default()
357 },
358 );
359
360 let renderer = Renderer::new(mock_render_config());
361 let output = renderer.render(&context).unwrap();
362
363 let build_file_content = output
364 .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
365 .unwrap();
366
367 assert!(build_file_content.contains("# Hello World from additive section!"));
368 }
369
370 #[test]
371 fn render_aliases() {
372 let annotations = Annotations::new(
373 test::metadata::alias(),
374 test::lockfile::alias(),
375 Config::default(),
376 )
377 .unwrap();
378 let context = Context::new(annotations).unwrap();
379
380 let renderer = Renderer::new(mock_render_config());
381 let output = renderer.render(&context).unwrap();
382
383 let build_file_content = output.get(&PathBuf::from("BUILD.bazel")).unwrap();
384
385 assert!(build_file_content.contains(r#"name = "names-0.12.1-dev__names","#));
386 assert!(build_file_content.contains(r#"name = "names-0.13.0__names","#));
387 }
388
389 #[test]
390 fn render_crate_repositories() {
391 let mut context = Context::default();
392 let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
393 context.crates.insert(
394 crate_id.clone(),
395 CrateContext {
396 name: crate_id.name,
397 version: crate_id.version,
398 targets: vec![Rule::Library(mock_target_attributes())],
399 ..CrateContext::default()
400 },
401 );
402
403 let renderer = Renderer::new(mock_render_config());
404 let output = renderer.render(&context).unwrap();
405
406 let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap();
407
408 assert!(defs_module.contains("def crate_repositories():"));
409 }
410
411 #[test]
412 fn remote_remote_vendor_mode() {
413 let mut context = Context::default();
414 let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
415 context.crates.insert(
416 crate_id.clone(),
417 CrateContext {
418 name: crate_id.name,
419 version: crate_id.version,
420 targets: vec![Rule::Library(mock_target_attributes())],
421 ..CrateContext::default()
422 },
423 );
424
425 // Enable remote vendor mode
426 let config = RenderConfig {
427 vendor_mode: Some(VendorMode::Remote),
428 ..mock_render_config()
429 };
430
431 let renderer = Renderer::new(config);
432 let output = renderer.render(&context).unwrap();
433
434 let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap();
435 assert!(defs_module.contains("def crate_repositories():"));
436
437 let crates_module = output.get(&PathBuf::from("crates.bzl")).unwrap();
438 assert!(crates_module.contains("def crate_repositories():"));
439 }
440
441 #[test]
442 fn remote_local_vendor_mode() {
443 let mut context = Context::default();
444 let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
445 context.crates.insert(
446 crate_id.clone(),
447 CrateContext {
448 name: crate_id.name,
449 version: crate_id.version,
450 targets: vec![Rule::Library(mock_target_attributes())],
451 ..CrateContext::default()
452 },
453 );
454
455 // Enable local vendor mode
456 let config = RenderConfig {
457 vendor_mode: Some(VendorMode::Local),
458 ..mock_render_config()
459 };
460
461 let renderer = Renderer::new(config);
462 let output = renderer.render(&context).unwrap();
463
464 // Local vendoring does not produce a `crate_repositories` macro
465 let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap();
466 assert!(!defs_module.contains("def crate_repositories():"));
467
468 // Local vendoring does not produce a `crates.bzl` file.
469 assert!(output.get(&PathBuf::from("crates.bzl")).is_none());
470 }
Brian Silverman5f6f2762022-08-13 19:30:05 -0700471
472 #[test]
473 fn duplicate_rustc_flags() {
474 let mut context = Context::default();
475 let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
476
477 let rustc_flags = vec![
478 "-l".to_owned(),
479 "dylib=ssl".to_owned(),
480 "-l".to_owned(),
481 "dylib=crypto".to_owned(),
482 ];
483
484 context.crates.insert(
485 crate_id.clone(),
486 CrateContext {
487 name: crate_id.name,
488 version: crate_id.version,
489 targets: vec![Rule::Library(mock_target_attributes())],
490 common_attrs: CommonAttributes {
491 rustc_flags: rustc_flags.clone(),
492 ..CommonAttributes::default()
493 },
494 ..CrateContext::default()
495 },
496 );
497
498 // Enable local vendor mode
499 let config = RenderConfig {
500 vendor_mode: Some(VendorMode::Local),
501 ..mock_render_config()
502 };
503
504 let renderer = Renderer::new(config);
505 let output = renderer.render(&context).unwrap();
506
507 let build_file_content = output
508 .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
509 .unwrap();
510
511 // Strip all spaces from the generated BUILD file and ensure it has the flags
512 // represented by `rustc_flags` in the same order.
513 assert!(build_file_content.replace(' ', "").contains(
514 &rustc_flags
515 .iter()
516 .map(|s| format!("\"{}\",", s))
517 .collect::<Vec<String>>()
518 .join("\n")
519 ));
520 }
Brian Silvermancc09f182022-03-09 15:40:20 -0800521}