blob: 3785b3479478d4830e8358e0b4c00eb0c7c3b413 [file] [log] [blame]
Brian Silvermancc09f182022-03-09 15:40:20 -08001//! A template engine backed by [Tera] for rendering Files.
2
3use std::collections::HashMap;
4
5use anyhow::{Context as AnyhowContext, Result};
6use serde_json::{from_value, to_value, Value};
7use tera::{self, Tera};
8
9use crate::config::{CrateId, RenderConfig};
10use crate::context::Context;
11use crate::rendering::{
12 render_crate_bazel_label, render_crate_bazel_repository, render_crate_build_file,
13 render_module_label, render_platform_constraint_label,
14};
15use crate::utils::sanitize_module_name;
16use crate::utils::sanitize_repository_name;
17use crate::utils::starlark::{SelectStringDict, SelectStringList};
18
19pub struct TemplateEngine {
20 engine: Tera,
21 context: tera::Context,
22}
23
24impl TemplateEngine {
25 pub fn new(render_config: &RenderConfig) -> Self {
26 let mut tera = Tera::default();
27 tera.add_raw_templates(vec![
28 (
29 "partials/crate/aliases.j2",
30 include_str!(concat!(
31 env!("CARGO_MANIFEST_DIR"),
32 "/src/rendering/templates/partials/crate/aliases.j2"
33 )),
34 ),
35 (
36 "partials/crate/binary.j2",
37 include_str!(concat!(
38 env!("CARGO_MANIFEST_DIR"),
39 "/src/rendering/templates/partials/crate/binary.j2"
40 )),
41 ),
42 (
43 "partials/crate/build_script.j2",
44 include_str!(concat!(
45 env!("CARGO_MANIFEST_DIR"),
46 "/src/rendering/templates/partials/crate/build_script.j2"
47 )),
48 ),
49 (
50 "partials/crate/common_attrs.j2",
51 include_str!(concat!(
52 env!("CARGO_MANIFEST_DIR"),
53 "/src/rendering/templates/partials/crate/common_attrs.j2"
54 )),
55 ),
56 (
57 "partials/crate/deps.j2",
58 include_str!(concat!(
59 env!("CARGO_MANIFEST_DIR"),
60 "/src/rendering/templates/partials/crate/deps.j2"
61 )),
62 ),
63 (
64 "partials/crate/library.j2",
65 include_str!(concat!(
66 env!("CARGO_MANIFEST_DIR"),
67 "/src/rendering/templates/partials/crate/library.j2"
68 )),
69 ),
70 (
71 "partials/crate/proc_macro.j2",
72 include_str!(concat!(
73 env!("CARGO_MANIFEST_DIR"),
74 "/src/rendering/templates/partials/crate/proc_macro.j2"
75 )),
76 ),
77 (
78 "partials/module/aliases_map.j2",
79 include_str!(concat!(
80 env!("CARGO_MANIFEST_DIR"),
81 "/src/rendering/templates/partials/module/aliases_map.j2"
82 )),
83 ),
84 (
85 "partials/module/deps_map.j2",
86 include_str!(concat!(
87 env!("CARGO_MANIFEST_DIR"),
88 "/src/rendering/templates/partials/module/deps_map.j2"
89 )),
90 ),
91 (
92 "partials/module/repo_git.j2",
93 include_str!(concat!(
94 env!("CARGO_MANIFEST_DIR"),
95 "/src/rendering/templates/partials/module/repo_git.j2"
96 )),
97 ),
98 (
99 "partials/module/repo_http.j2",
100 include_str!(concat!(
101 env!("CARGO_MANIFEST_DIR"),
102 "/src/rendering/templates/partials/module/repo_http.j2"
103 )),
104 ),
105 (
106 "partials/starlark/glob.j2",
107 include_str!(concat!(
108 env!("CARGO_MANIFEST_DIR"),
109 "/src/rendering/templates/partials/starlark/glob.j2"
110 )),
111 ),
112 (
113 "partials/starlark/selectable_dict.j2",
114 include_str!(concat!(
115 env!("CARGO_MANIFEST_DIR"),
116 "/src/rendering/templates/partials/starlark/selectable_dict.j2"
117 )),
118 ),
119 (
120 "partials/starlark/selectable_list.j2",
121 include_str!(concat!(
122 env!("CARGO_MANIFEST_DIR"),
123 "/src/rendering/templates/partials/starlark/selectable_list.j2"
124 )),
125 ),
126 (
127 "partials/header.j2",
128 include_str!(concat!(
129 env!("CARGO_MANIFEST_DIR"),
130 "/src/rendering/templates/partials/header.j2"
131 )),
132 ),
133 (
134 "crate_build_file.j2",
135 include_str!(concat!(
136 env!("CARGO_MANIFEST_DIR"),
137 "/src/rendering/templates/crate_build_file.j2"
138 )),
139 ),
140 (
141 "module_build_file.j2",
142 include_str!(concat!(
143 env!("CARGO_MANIFEST_DIR"),
144 "/src/rendering/templates/module_build_file.j2"
145 )),
146 ),
147 (
148 "module_bzl.j2",
149 include_str!(concat!(
150 env!("CARGO_MANIFEST_DIR"),
151 "/src/rendering/templates/module_bzl.j2"
152 )),
153 ),
154 (
155 "vendor_module.j2",
156 include_str!(concat!(
157 env!("CARGO_MANIFEST_DIR"),
158 "/src/rendering/templates/vendor_module.j2"
159 )),
160 ),
161 ])
162 .unwrap();
163
164 tera.register_function(
165 "crate_build_file",
166 crate_build_file_fn_generator(render_config.build_file_template.clone()),
167 );
168 tera.register_function(
169 "crate_label",
170 crate_label_fn_generator(
171 render_config.crate_label_template.clone(),
172 render_config.repository_name.clone(),
173 ),
174 );
175 tera.register_function(
176 "crate_repository",
177 crate_repository_fn_generator(
178 render_config.crate_repository_template.clone(),
179 render_config.repository_name.clone(),
180 ),
181 );
182 tera.register_function(
183 "platform_label",
184 platform_label_fn_generator(render_config.platforms_template.clone()),
185 );
186 tera.register_function("sanitize_module_name", sanitize_module_name_fn);
187 tera.register_function(
188 "crates_module_label",
189 module_label_fn_generator(render_config.crates_module_template.clone()),
190 );
191
192 let mut context = tera::Context::new();
193 context.insert("default_select_list", &SelectStringList::default());
194 context.insert("default_select_dict", &SelectStringDict::default());
195 context.insert("repository_name", &render_config.repository_name);
196 context.insert("vendor_mode", &render_config.vendor_mode);
Brian Silverman5f6f2762022-08-13 19:30:05 -0700197 context.insert("regen_command", &render_config.regen_command);
Brian Silvermancc09f182022-03-09 15:40:20 -0800198 context.insert("Null", &tera::Value::Null);
199 context.insert(
200 "default_package_name",
201 &match render_config.default_package_name.as_ref() {
202 Some(pkg_name) => format!("\"{}\"", pkg_name),
203 None => "None".to_owned(),
204 },
205 );
206
207 Self {
208 engine: tera,
209 context,
210 }
211 }
212
213 fn new_tera_ctx(&self) -> tera::Context {
214 self.context.clone()
215 }
216
217 pub fn render_crate_build_files<'a>(
218 &self,
219 ctx: &'a Context,
220 ) -> Result<HashMap<&'a CrateId, String>> {
221 // Create the render context with the global planned context to be
222 // reused when rendering crates.
223 let mut context = self.new_tera_ctx();
224 context.insert("context", ctx);
225
226 ctx.crates
227 .iter()
228 .map(|(id, _)| {
229 let aliases = ctx.crate_aliases(id, false, false);
230 let build_aliases = ctx.crate_aliases(id, true, false);
231
232 context.insert("crate_id", &id);
233 context.insert("common_aliases", &aliases);
234 context.insert("build_aliases", &build_aliases);
235
236 let content = self
237 .engine
238 .render("crate_build_file.j2", &context)
239 .context("Failed to render BUILD file")?;
240
241 Ok((id, content))
242 })
243 .collect()
244 }
245
246 pub fn render_module_build_file(&self, data: &Context) -> Result<String> {
247 let mut context = self.new_tera_ctx();
248 context.insert("context", data);
249
250 let workspace_member_deps = data.flat_workspace_member_deps();
251 context.insert("workspace_member_dependencies", &workspace_member_deps);
252
253 let binary_crates_map = data.flat_binary_deps();
254 context.insert("binary_crates_map", &binary_crates_map);
255
256 self.engine
257 .render("module_build_file.j2", &context)
258 .context("Failed to render crates module")
259 }
260
261 pub fn render_module_bzl(&self, data: &Context) -> Result<String> {
262 let mut context = self.new_tera_ctx();
263 context.insert("context", data);
264
265 self.engine
266 .render("module_bzl.j2", &context)
267 .context("Failed to render crates module")
268 }
269
270 pub fn render_vendor_module_file(&self, data: &Context) -> Result<String> {
271 let mut context = self.new_tera_ctx();
272 context.insert("context", data);
273
274 self.engine
275 .render("vendor_module.j2", &context)
276 .context("Failed to render vendor module")
277 }
278}
279
280/// A convienience wrapper for parsing parameters to tera functions
281macro_rules! parse_tera_param {
282 ($param:literal, $param_type:ty, $args:ident) => {
283 match $args.get($param) {
284 Some(val) => match from_value::<$param_type>(val.clone()) {
285 Ok(v) => v,
286 Err(_) => {
287 return Err(tera::Error::msg(format!(
288 "The `{}` paramater could not be parsed as a String.",
289 $param
290 )))
291 }
292 },
293 None => {
294 return Err(tera::Error::msg(format!(
295 "No `{}` parameter was passed.",
296 $param
297 )))
298 }
299 }
300 };
301}
302
303/// Convert a crate name into a module name by applying transforms to invalid characters.
304fn sanitize_module_name_fn(args: &HashMap<String, Value>) -> tera::Result<Value> {
305 let crate_name = parse_tera_param!("crate_name", String, args);
306
307 match to_value(sanitize_module_name(&crate_name)) {
308 Ok(v) => Ok(v),
309 Err(_) => Err(tera::Error::msg("Failed to generate resulting module name")),
310 }
311}
312
313/// Convert a crate name into a module name by applying transforms to invalid characters.
314fn platform_label_fn_generator(template: String) -> impl tera::Function {
315 Box::new(
316 move |args: &HashMap<String, Value>| -> tera::Result<Value> {
317 let triple = parse_tera_param!("triple", String, args);
318 match to_value(render_platform_constraint_label(&template, &triple)) {
319 Ok(v) => Ok(v),
320 Err(_) => Err(tera::Error::msg("Failed to generate resulting module name")),
321 }
322 },
323 )
324}
325
326/// Convert a crate name into a module name by applying transforms to invalid characters.
327fn crate_build_file_fn_generator(template: String) -> impl tera::Function {
328 Box::new(
329 move |args: &HashMap<String, Value>| -> tera::Result<Value> {
330 let name = parse_tera_param!("name", String, args);
331 let version = parse_tera_param!("version", String, args);
332
333 match to_value(render_crate_build_file(&template, &name, &version)) {
334 Ok(v) => Ok(v),
335 Err(_) => Err(tera::Error::msg("Failed to generate crate's BUILD file")),
336 }
337 },
338 )
339}
340
341/// Convert a file name to a Bazel label
342fn module_label_fn_generator(template: String) -> impl tera::Function {
343 Box::new(
344 move |args: &HashMap<String, Value>| -> tera::Result<Value> {
345 let file = parse_tera_param!("file", String, args);
346
347 let label = match render_module_label(&template, &file) {
348 Ok(v) => v,
349 Err(e) => return Err(tera::Error::msg(e)),
350 };
351
352 match to_value(label.to_string()) {
353 Ok(v) => Ok(v),
354 Err(_) => Err(tera::Error::msg("Failed to generate crate's BUILD file")),
355 }
356 },
357 )
358}
359
360/// Convert a crate name into a module name by applying transforms to invalid characters.
361fn crate_label_fn_generator(template: String, repository_name: String) -> impl tera::Function {
362 Box::new(
363 move |args: &HashMap<String, Value>| -> tera::Result<Value> {
364 let name = parse_tera_param!("name", String, args);
365 let version = parse_tera_param!("version", String, args);
366 let target = parse_tera_param!("target", String, args);
367
368 match to_value(sanitize_repository_name(&render_crate_bazel_label(
369 &template,
370 &repository_name,
371 &name,
372 &version,
373 &target,
374 ))) {
375 Ok(v) => Ok(v),
376 Err(_) => Err(tera::Error::msg("Failed to generate crate's label")),
377 }
378 },
379 )
380}
381
382/// Convert a crate name into a module name by applying transforms to invalid characters.
383fn crate_repository_fn_generator(template: String, repository_name: String) -> impl tera::Function {
384 Box::new(
385 move |args: &HashMap<String, Value>| -> tera::Result<Value> {
386 let name = parse_tera_param!("name", String, args);
387 let version = parse_tera_param!("version", String, args);
388
389 match to_value(sanitize_repository_name(&render_crate_bazel_repository(
390 &template,
391 &repository_name,
392 &name,
393 &version,
394 ))) {
395 Ok(v) => Ok(v),
396 Err(_) => Err(tera::Error::msg("Failed to generate crate repository name")),
397 }
398 },
399 )
400}