Squashed 'third_party/rules_rust/' content from commit bf59038ca

git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/crate_universe/src/rendering.rs b/crate_universe/src/rendering.rs
new file mode 100644
index 0000000..a0570ba
--- /dev/null
+++ b/crate_universe/src/rendering.rs
@@ -0,0 +1,470 @@
+//! Tools for rendering and writing BUILD and other Starlark files
+
+mod template_engine;
+
+use std::collections::BTreeMap;
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+
+use anyhow::{bail, Context as AnyhowContext, Result};
+
+use crate::config::RenderConfig;
+use crate::context::Context;
+use crate::rendering::template_engine::TemplateEngine;
+use crate::splicing::default_splicing_package_crate_id;
+use crate::utils::starlark::Label;
+
+pub struct Renderer {
+    config: RenderConfig,
+    engine: TemplateEngine,
+}
+
+impl Renderer {
+    pub fn new(config: RenderConfig) -> Self {
+        let engine = TemplateEngine::new(&config);
+        Self { config, engine }
+    }
+
+    pub fn render(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
+        let mut output = BTreeMap::new();
+
+        output.extend(self.render_build_files(context)?);
+        output.extend(self.render_crates_module(context)?);
+
+        if let Some(vendor_mode) = &self.config.vendor_mode {
+            match vendor_mode {
+                crate::config::VendorMode::Local => {
+                    // Nothing to do for local vendor crate
+                }
+                crate::config::VendorMode::Remote => {
+                    output.extend(self.render_vendor_support_files(context)?);
+                }
+            }
+        }
+
+        Ok(output)
+    }
+
+    fn render_crates_module(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
+        let module_label = render_module_label(&self.config.crates_module_template, "defs.bzl")
+            .context("Failed to resolve string to module file label")?;
+        let module_build_label =
+            render_module_label(&self.config.crates_module_template, "BUILD.bazel")
+                .context("Failed to resolve string to module file label")?;
+
+        let mut map = BTreeMap::new();
+        map.insert(
+            Renderer::label_to_path(&module_label),
+            self.engine.render_module_bzl(context)?,
+        );
+        map.insert(
+            Renderer::label_to_path(&module_build_label),
+            self.engine.render_module_build_file(context)?,
+        );
+
+        Ok(map)
+    }
+
+    fn render_build_files(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
+        let default_splicing_package_id = default_splicing_package_crate_id();
+        self.engine
+            .render_crate_build_files(context)?
+            .into_iter()
+            // Do not render the default splicing package
+            .filter(|(id, _)| *id != &default_splicing_package_id)
+            // Do not render local packages
+            .filter(|(id, _)| !context.workspace_members.contains_key(id))
+            .map(|(id, content)| {
+                let ctx = &context.crates[id];
+                let label = match render_build_file_template(
+                    &self.config.build_file_template,
+                    &ctx.name,
+                    &ctx.version,
+                ) {
+                    Ok(label) => label,
+                    Err(e) => bail!(e),
+                };
+
+                let filename = Renderer::label_to_path(&label);
+
+                Ok((filename, content))
+            })
+            .collect()
+    }
+
+    fn render_vendor_support_files(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
+        let module_label = render_module_label(&self.config.crates_module_template, "crates.bzl")
+            .context("Failed to resolve string to module file label")?;
+
+        let mut map = BTreeMap::new();
+        map.insert(
+            Renderer::label_to_path(&module_label),
+            self.engine.render_vendor_module_file(context)?,
+        );
+
+        Ok(map)
+    }
+
+    fn label_to_path(label: &Label) -> PathBuf {
+        match &label.package {
+            Some(package) => PathBuf::from(format!("{}/{}", package, label.target)),
+            None => PathBuf::from(&label.target),
+        }
+    }
+}
+
+/// Write a set of [CrateContext][crate::context::CrateContext] to disk.
+pub fn write_outputs(
+    outputs: BTreeMap<PathBuf, String>,
+    out_dir: &Path,
+    dry_run: bool,
+) -> Result<()> {
+    let outputs: BTreeMap<PathBuf, String> = outputs
+        .into_iter()
+        .map(|(path, content)| (out_dir.join(path), content))
+        .collect();
+
+    if dry_run {
+        for (path, content) in outputs {
+            println!(
+                "==============================================================================="
+            );
+            println!("{}", path.display());
+            println!(
+                "==============================================================================="
+            );
+            println!("{}\n", content);
+        }
+    } else {
+        for (path, content) in outputs {
+            // Ensure the output directory exists
+            fs::create_dir_all(
+                path.parent()
+                    .expect("All file paths should have valid directories"),
+            )?;
+
+            fs::write(&path, content.as_bytes())
+                .context(format!("Failed to write file to disk: {}", path.display()))?;
+        }
+    }
+
+    Ok(())
+}
+
+/// Render the Bazel label of a crate
+pub fn render_crate_bazel_label(
+    template: &str,
+    repository_name: &str,
+    name: &str,
+    version: &str,
+    target: &str,
+) -> String {
+    template
+        .replace("{repository}", repository_name)
+        .replace("{name}", name)
+        .replace("{version}", version)
+        .replace("{target}", target)
+}
+
+/// Render the Bazel label of a crate
+pub fn render_crate_bazel_repository(
+    template: &str,
+    repository_name: &str,
+    name: &str,
+    version: &str,
+) -> String {
+    template
+        .replace("{repository}", repository_name)
+        .replace("{name}", name)
+        .replace("{version}", version)
+}
+
+/// Render the Bazel label of a crate
+pub fn render_crate_build_file(template: &str, name: &str, version: &str) -> String {
+    template
+        .replace("{name}", name)
+        .replace("{version}", version)
+}
+
+/// Render the Bazel label of a vendor module label
+pub fn render_module_label(template: &str, name: &str) -> Result<Label> {
+    Label::from_str(&template.replace("{file}", name))
+}
+
+/// Render the Bazel label of a platform triple
+pub fn render_platform_constraint_label(template: &str, triple: &str) -> String {
+    template.replace("{triple}", triple)
+}
+
+fn render_build_file_template(template: &str, name: &str, version: &str) -> Result<Label> {
+    Label::from_str(
+        &template
+            .replace("{name}", name)
+            .replace("{version}", version),
+    )
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    use crate::config::{Config, CrateId, VendorMode};
+    use crate::context::crate_context::{CrateContext, Rule};
+    use crate::context::{BuildScriptAttributes, Context, TargetAttributes};
+    use crate::metadata::Annotations;
+    use crate::test;
+
+    fn mock_render_config() -> RenderConfig {
+        serde_json::from_value(serde_json::json!({
+            "repository_name": "test_rendering"
+        }))
+        .unwrap()
+    }
+
+    fn mock_target_attributes() -> TargetAttributes {
+        TargetAttributes {
+            crate_name: "mock_crate".to_owned(),
+            crate_root: Some("src/root.rs".to_owned()),
+            ..TargetAttributes::default()
+        }
+    }
+
+    #[test]
+    fn render_rust_library() {
+        let mut context = Context::default();
+        let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
+        context.crates.insert(
+            crate_id.clone(),
+            CrateContext {
+                name: crate_id.name,
+                version: crate_id.version,
+                targets: vec![Rule::Library(mock_target_attributes())],
+                ..CrateContext::default()
+            },
+        );
+
+        let renderer = Renderer::new(mock_render_config());
+        let output = renderer.render(&context).unwrap();
+
+        let build_file_content = output
+            .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
+            .unwrap();
+
+        assert!(build_file_content.contains("rust_library("));
+        assert!(build_file_content.contains("name = \"mock_crate\""));
+    }
+
+    #[test]
+    fn render_cargo_build_script() {
+        let mut context = Context::default();
+        let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
+        context.crates.insert(
+            crate_id.clone(),
+            CrateContext {
+                name: crate_id.name,
+                version: crate_id.version,
+                targets: vec![Rule::BuildScript(TargetAttributes {
+                    crate_name: "build_script_build".to_owned(),
+                    crate_root: Some("build.rs".to_owned()),
+                    ..TargetAttributes::default()
+                })],
+                // Build script attributes are required.
+                build_script_attrs: Some(BuildScriptAttributes::default()),
+                ..CrateContext::default()
+            },
+        );
+
+        let renderer = Renderer::new(mock_render_config());
+        let output = renderer.render(&context).unwrap();
+
+        let build_file_content = output
+            .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
+            .unwrap();
+
+        assert!(build_file_content.contains("cargo_build_script("));
+        assert!(build_file_content.contains("name = \"build_script_build\""));
+
+        // Ensure `cargo_build_script` requirements are met
+        assert!(build_file_content.contains("name = \"mock_crate_build_script\""));
+    }
+
+    #[test]
+    fn render_proc_macro() {
+        let mut context = Context::default();
+        let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
+        context.crates.insert(
+            crate_id.clone(),
+            CrateContext {
+                name: crate_id.name,
+                version: crate_id.version,
+                targets: vec![Rule::ProcMacro(mock_target_attributes())],
+                ..CrateContext::default()
+            },
+        );
+
+        let renderer = Renderer::new(mock_render_config());
+        let output = renderer.render(&context).unwrap();
+
+        let build_file_content = output
+            .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
+            .unwrap();
+
+        assert!(build_file_content.contains("rust_proc_macro("));
+        assert!(build_file_content.contains("name = \"mock_crate\""));
+    }
+
+    #[test]
+    fn render_binary() {
+        let mut context = Context::default();
+        let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
+        context.crates.insert(
+            crate_id.clone(),
+            CrateContext {
+                name: crate_id.name,
+                version: crate_id.version,
+                targets: vec![Rule::Binary(mock_target_attributes())],
+                ..CrateContext::default()
+            },
+        );
+
+        let renderer = Renderer::new(mock_render_config());
+        let output = renderer.render(&context).unwrap();
+
+        let build_file_content = output
+            .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
+            .unwrap();
+
+        assert!(build_file_content.contains("rust_binary("));
+        assert!(build_file_content.contains("name = \"mock_crate__bin\""));
+    }
+
+    #[test]
+    fn render_additive_build_contents() {
+        let mut context = Context::default();
+        let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
+        context.crates.insert(
+            crate_id.clone(),
+            CrateContext {
+                name: crate_id.name,
+                version: crate_id.version,
+                targets: vec![Rule::Binary(mock_target_attributes())],
+                additive_build_file_content: Some(
+                    "# Hello World from additive section!".to_owned(),
+                ),
+                ..CrateContext::default()
+            },
+        );
+
+        let renderer = Renderer::new(mock_render_config());
+        let output = renderer.render(&context).unwrap();
+
+        let build_file_content = output
+            .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
+            .unwrap();
+
+        assert!(build_file_content.contains("# Hello World from additive section!"));
+    }
+
+    #[test]
+    fn render_aliases() {
+        let annotations = Annotations::new(
+            test::metadata::alias(),
+            test::lockfile::alias(),
+            Config::default(),
+        )
+        .unwrap();
+        let context = Context::new(annotations).unwrap();
+
+        let renderer = Renderer::new(mock_render_config());
+        let output = renderer.render(&context).unwrap();
+
+        let build_file_content = output.get(&PathBuf::from("BUILD.bazel")).unwrap();
+
+        assert!(build_file_content.contains(r#"name = "names-0.12.1-dev__names","#));
+        assert!(build_file_content.contains(r#"name = "names-0.13.0__names","#));
+    }
+
+    #[test]
+    fn render_crate_repositories() {
+        let mut context = Context::default();
+        let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
+        context.crates.insert(
+            crate_id.clone(),
+            CrateContext {
+                name: crate_id.name,
+                version: crate_id.version,
+                targets: vec![Rule::Library(mock_target_attributes())],
+                ..CrateContext::default()
+            },
+        );
+
+        let renderer = Renderer::new(mock_render_config());
+        let output = renderer.render(&context).unwrap();
+
+        let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap();
+
+        assert!(defs_module.contains("def crate_repositories():"));
+    }
+
+    #[test]
+    fn remote_remote_vendor_mode() {
+        let mut context = Context::default();
+        let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
+        context.crates.insert(
+            crate_id.clone(),
+            CrateContext {
+                name: crate_id.name,
+                version: crate_id.version,
+                targets: vec![Rule::Library(mock_target_attributes())],
+                ..CrateContext::default()
+            },
+        );
+
+        // Enable remote vendor mode
+        let config = RenderConfig {
+            vendor_mode: Some(VendorMode::Remote),
+            ..mock_render_config()
+        };
+
+        let renderer = Renderer::new(config);
+        let output = renderer.render(&context).unwrap();
+
+        let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap();
+        assert!(defs_module.contains("def crate_repositories():"));
+
+        let crates_module = output.get(&PathBuf::from("crates.bzl")).unwrap();
+        assert!(crates_module.contains("def crate_repositories():"));
+    }
+
+    #[test]
+    fn remote_local_vendor_mode() {
+        let mut context = Context::default();
+        let crate_id = CrateId::new("mock_crate".to_owned(), "0.1.0".to_owned());
+        context.crates.insert(
+            crate_id.clone(),
+            CrateContext {
+                name: crate_id.name,
+                version: crate_id.version,
+                targets: vec![Rule::Library(mock_target_attributes())],
+                ..CrateContext::default()
+            },
+        );
+
+        // Enable local vendor mode
+        let config = RenderConfig {
+            vendor_mode: Some(VendorMode::Local),
+            ..mock_render_config()
+        };
+
+        let renderer = Renderer::new(config);
+        let output = renderer.render(&context).unwrap();
+
+        // Local vendoring does not produce a `crate_repositories` macro
+        let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap();
+        assert!(!defs_module.contains("def crate_repositories():"));
+
+        // Local vendoring does not produce a `crates.bzl` file.
+        assert!(output.get(&PathBuf::from("crates.bzl")).is_none());
+    }
+}