Convert BUILD.$name-$version.bazel to serde_starlark (#1743)

Context: see #1734.

This PR converts all of `crate_build_file.j2`, `partials/crate/*.j2` and `partials/starlark/*.j2` to serde_starlark.

### Remarks

- I kept the output of `bazel run //crate_universe/3rdparty:crates_vendor` out of this PR to keep GitHub's code review interface usable. You can find the output generated code in 44c264a7845701239e50d643b78b2bf4a117d425.

- I tried to keep the conversion 1-to-1 to the largest extent possible. As an example, build_script.j2 duplicated most of the content of common_attrs.j2 even though it probably could have used that template via include, and I have mirrored the same here by not using `#[serde(flatten)] common: CommonAttrs` inside of `CargoBuildScript`. There is probably plenty of opportunity for cleaning up things in followup PRs.

- I came across several bugs in the existing code. Again, I mostly stayed away from these to keep the PR bounded in scope. For example the fact that `rustc_flags` is stored as a BTreeSet\<String\> is not correct; compiler flags are ordered and sorting them is not correct. For example `-C debuginfo=2`...

    https://github.com/bazelbuild/rules_rust/blob/532e60ff0ee3ac318333b3ae05392c220b17ce28/crate_universe/src/context/crate_context.rs#L178

- One bug that I fixed because in the new implementation it was easier to fix than to not fix, is that all existing uses of selectable_list.j2 and selectable_dict.j2 were broken because they all had the same failure mode as #1417. The generated code using `selects.with_or` could end up generating a map with duplicate keys if multiple distinct Cargo cfg expressions mapped to the same subset of supported platform triples. My implementation follows the same approach as #1647, but that PR only applied it to `deps` and `aliases`, not e.g. `rustc_env` and everything else.

- Anecdotally the new implementation has strong "if it compiles, it works" property. I mostly typed out the implementation of this PR top to bottom without running `crates_vendor`, and as soon as the whole thing was written and the compiler was okay with it, it was generating the output I wanted. I have not done much work in the templates in the past but I do not imagine they have this property.

### Remaining work

- `select_with_or` in private/selects.bzl is no longer used by the code generator's output. I had to keep it in this PR because all the generated BUILD files still include calls to it. It can be deleted after all those are regenerated.

- One of the tera templates still uses the old serialization of `SelectList` so the starlark serialization for it isn't a normal `Serialize` impl but instead an associated function. This causes there to be a bunch of `serialize_with = "SelectList::serialize_starlark"` attributes needed. This can be cleaned up after the last tera template is converted.

- ~~A lot of the contents of `crate::context` can be cleaned up after they're no longer used by tera. Tera needs those data structures to be serializable, whereas with serde_starlark we're not serializing anything from `crate::context`, only from `crate::utils::starlark`. For now I've just added a bunch of `serde(skip)` attributes in `crate::context` on all the parts tera is no longer using.~~ *Edit: apparently we still need to serialize all the fields in `Context` so that they end up in cargo-bazel-lock.json.*

- There are 2 remaining tera templates after this PR, module_bzl.j2 and vendor_module.j2.
diff --git a/crate_universe/src/context.rs b/crate_universe/src/context.rs
index d3d01d0..b71e646 100644
--- a/crate_universe/src/context.rs
+++ b/crate_universe/src/context.rs
@@ -15,7 +15,7 @@
 use crate::context::platforms::resolve_cfg_platforms;
 use crate::lockfile::Digest;
 use crate::metadata::Annotations;
-use crate::utils::starlark::{Select, SelectList};
+use crate::utils::starlark::Select;
 
 pub use self::crate_context::*;
 
@@ -159,120 +159,6 @@
         Ok(package_path_id)
     }
 
-    /// Filter a crate's dependencies to only ones with aliases
-    pub fn crate_aliases(
-        &self,
-        crate_id: &CrateId,
-        build: bool,
-        include_dev: bool,
-    ) -> SelectList<&CrateDependency> {
-        let ctx = &self.crates[crate_id];
-        let mut set = SelectList::default();
-
-        // Return a set of aliases for build dependencies
-        // vs normal dependencies when requested.
-        if build {
-            // Note that there may not be build dependencies so no dependencies
-            // will be gathered in this case
-            if let Some(attrs) = &ctx.build_script_attrs {
-                let collection: Vec<(Option<String>, &CrateDependency)> = attrs
-                    .deps
-                    .configurations()
-                    .into_iter()
-                    .flat_map(move |conf| {
-                        attrs
-                            .deps
-                            .get_iter(conf)
-                            .expect("Iterating over known keys should never panic")
-                            .filter(|dep| dep.alias.is_some())
-                            .map(move |dep| (conf.cloned(), dep))
-                    })
-                    .chain(attrs.proc_macro_deps.configurations().into_iter().flat_map(
-                        move |conf| {
-                            attrs
-                                .proc_macro_deps
-                                .get_iter(conf)
-                                .expect("Iterating over known keys should never panic")
-                                .filter(|dep| dep.alias.is_some())
-                                .map(move |dep| (conf.cloned(), dep))
-                        },
-                    ))
-                    .collect();
-
-                for (config, dep) in collection {
-                    set.insert(dep, config);
-                }
-            }
-        } else {
-            let attrs = &ctx.common_attrs;
-            let mut collection: Vec<(Option<String>, &CrateDependency)> =
-                attrs
-                    .deps
-                    .configurations()
-                    .into_iter()
-                    .flat_map(move |conf| {
-                        attrs
-                            .deps
-                            .get_iter(conf)
-                            .expect("Iterating over known keys should never panic")
-                            .filter(|dep| dep.alias.is_some())
-                            .map(move |dep| (conf.cloned(), dep))
-                    })
-                    .chain(attrs.proc_macro_deps.configurations().into_iter().flat_map(
-                        move |conf| {
-                            attrs
-                                .proc_macro_deps
-                                .get_iter(conf)
-                                .expect("Iterating over known keys should never panic")
-                                .filter(|dep| dep.alias.is_some())
-                                .map(move |dep| (conf.cloned(), dep))
-                        },
-                    ))
-                    .collect();
-
-            // Optionally include dev dependencies
-            if include_dev {
-                collection = collection
-                    .into_iter()
-                    .chain(
-                        attrs
-                            .deps_dev
-                            .configurations()
-                            .into_iter()
-                            .flat_map(move |conf| {
-                                attrs
-                                    .deps_dev
-                                    .get_iter(conf)
-                                    .expect("Iterating over known keys should never panic")
-                                    .filter(|dep| dep.alias.is_some())
-                                    .map(move |dep| (conf.cloned(), dep))
-                            }),
-                    )
-                    .chain(
-                        attrs
-                            .proc_macro_deps_dev
-                            .configurations()
-                            .into_iter()
-                            .flat_map(move |conf| {
-                                attrs
-                                    .proc_macro_deps_dev
-                                    .get_iter(conf)
-                                    .expect("Iterating over known keys should never panic")
-                                    .filter(|dep| dep.alias.is_some())
-                                    .map(move |dep| (conf.cloned(), dep))
-                            }),
-                    )
-                    .collect();
-            }
-
-            for (config, dep) in collection {
-                set.insert(dep, config);
-            }
-        }
-
-        set
-    }
-
     /// Create a set of all direct dependencies of workspace member crates.
     pub fn workspace_member_deps(&self) -> BTreeSet<&CrateDependency> {
         self.workspace_members
diff --git a/crate_universe/src/context/crate_context.rs b/crate_universe/src/context/crate_context.rs
index dab70e7..df33c54 100644
--- a/crate_universe/src/context/crate_context.rs
+++ b/crate_universe/src/context/crate_context.rs
@@ -63,7 +63,7 @@
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
 #[serde(default)]
 pub struct CommonAttributes {
-    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectStringList::is_empty")]
     pub compile_data: SelectStringList,
 
     #[serde(skip_serializing_if = "BTreeSet::is_empty")]
@@ -72,19 +72,19 @@
     #[serde(skip_serializing_if = "BTreeSet::is_empty")]
     pub crate_features: BTreeSet<String>,
 
-    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectStringList::is_empty")]
     pub data: SelectStringList,
 
     #[serde(skip_serializing_if = "BTreeSet::is_empty")]
     pub data_glob: BTreeSet<String>,
 
-    #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectList::is_empty")]
     pub deps: SelectList<CrateDependency>,
 
     #[serde(skip_serializing_if = "BTreeSet::is_empty")]
     pub extra_deps: BTreeSet<String>,
 
-    #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectList::is_empty")]
     pub deps_dev: SelectList<CrateDependency>,
 
     pub edition: String,
@@ -92,19 +92,19 @@
     #[serde(skip_serializing_if = "Option::is_none")]
     pub linker_script: Option<String>,
 
-    #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectList::is_empty")]
     pub proc_macro_deps: SelectList<CrateDependency>,
 
     #[serde(skip_serializing_if = "BTreeSet::is_empty")]
     pub extra_proc_macro_deps: BTreeSet<String>,
 
-    #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectList::is_empty")]
     pub proc_macro_deps_dev: SelectList<CrateDependency>,
 
-    #[serde(skip_serializing_if = "SelectStringDict::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectStringDict::is_empty")]
     pub rustc_env: SelectStringDict,
 
-    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectStringList::is_empty")]
     pub rustc_env_files: SelectStringList,
 
     #[serde(skip_serializing_if = "Vec::is_empty")]
@@ -147,40 +147,40 @@
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
 #[serde(default)]
 pub struct BuildScriptAttributes {
-    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectStringList::is_empty")]
     pub compile_data: SelectStringList,
 
-    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectStringList::is_empty")]
     pub data: SelectStringList,
 
     #[serde(skip_serializing_if = "BTreeSet::is_empty")]
     pub data_glob: BTreeSet<String>,
 
-    #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectList::is_empty")]
     pub deps: SelectList<CrateDependency>,
 
     #[serde(skip_serializing_if = "BTreeSet::is_empty")]
     pub extra_deps: BTreeSet<String>,
 
-    #[serde(skip_serializing_if = "SelectStringDict::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectStringDict::is_empty")]
     pub build_script_env: SelectStringDict,
 
     #[serde(skip_serializing_if = "BTreeSet::is_empty")]
     pub extra_proc_macro_deps: BTreeSet<String>,
 
-    #[serde(skip_serializing_if = "SelectList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectList::is_empty")]
     pub proc_macro_deps: SelectList<CrateDependency>,
 
-    #[serde(skip_serializing_if = "SelectStringDict::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectStringDict::is_empty")]
     pub rustc_env: SelectStringDict,
 
-    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectStringList::is_empty")]
     pub rustc_flags: SelectStringList,
 
-    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectStringList::is_empty")]
     pub rustc_env_files: SelectStringList,
 
-    #[serde(skip_serializing_if = "SelectStringList::should_skip_serializing")]
+    #[serde(skip_serializing_if = "SelectStringList::is_empty")]
     pub tools: SelectStringList,
 
     #[serde(skip_serializing_if = "Option::is_none")]
@@ -453,7 +453,7 @@
 
             // Rustc env
             if let Some(extra) = &crate_extra.rustc_env {
-                self.common_attrs.rustc_env.insert(extra.clone(), None);
+                self.common_attrs.rustc_env.extend(extra.clone(), None);
             }
 
             // Rustc env files
@@ -503,12 +503,12 @@
 
                 // Rustc env
                 if let Some(extra) = &crate_extra.build_script_rustc_env {
-                    attrs.rustc_env.insert(extra.clone(), None);
+                    attrs.rustc_env.extend(extra.clone(), None);
                 }
 
                 // Build script env
                 if let Some(extra) = &crate_extra.build_script_env {
-                    attrs.build_script_env.insert(extra.clone(), None);
+                    attrs.build_script_env.extend(extra.clone(), None);
                 }
             }
 
diff --git a/crate_universe/src/lib.rs b/crate_universe/src/lib.rs
index ae85717..e6fbd6f 100644
--- a/crate_universe/src/lib.rs
+++ b/crate_universe/src/lib.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::large_enum_variant)]
+
 pub mod cli;
 
 mod config;
diff --git a/crate_universe/src/rendering.rs b/crate_universe/src/rendering.rs
index a2eacff..a8de92f 100644
--- a/crate_universe/src/rendering.rs
+++ b/crate_universe/src/rendering.rs
@@ -4,19 +4,28 @@
 
 use std::collections::{BTreeMap, BTreeSet};
 use std::fs;
+use std::iter::FromIterator;
 use std::path::{Path, PathBuf};
 use std::str::FromStr;
 
 use anyhow::{bail, Context as AnyhowContext, Result};
+use indoc::formatdoc;
 
 use crate::config::{RenderConfig, VendorMode};
-use crate::context::crate_context::{CrateContext, Rule};
-use crate::context::Context;
+use crate::context::crate_context::{CrateContext, CrateDependency, Rule};
+use crate::context::{Context, TargetAttributes};
 use crate::rendering::template_engine::TemplateEngine;
 use crate::splicing::default_splicing_package_crate_id;
-use crate::utils::sanitize_repository_name;
-use crate::utils::starlark::Label;
-use crate::utils::starlark::{self, Alias, ExportsFiles, Filegroup, Glob, Package, Starlark};
+use crate::utils::starlark::{
+    self, Alias, CargoBuildScript, CommonAttrs, Data, ExportsFiles, Filegroup, Glob, Label, Load,
+    Package, RustBinary, RustLibrary, RustProcMacro, Select, SelectDict, SelectList, SelectMap,
+    Starlark,
+};
+use crate::utils::{self, sanitize_repository_name};
+
+// Configuration remapper used to convert from cfg expressions like "cfg(unix)"
+// to platform labels like "@rules_rust//rust/platform:x86_64-unknown-linux-gnu".
+type Platforms = BTreeMap<String, BTreeSet<String>>;
 
 pub struct Renderer {
     config: RenderConfig,
@@ -32,7 +41,8 @@
     pub fn render(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
         let mut output = BTreeMap::new();
 
-        output.extend(self.render_build_files(context)?);
+        let platforms = self.render_platform_labels(context);
+        output.extend(self.render_build_files(context, &platforms)?);
         output.extend(self.render_crates_module(context)?);
 
         if let Some(vendor_mode) = &self.config.vendor_mode {
@@ -49,6 +59,27 @@
         Ok(output)
     }
 
+    fn render_platform_labels(&self, context: &Context) -> BTreeMap<String, BTreeSet<String>> {
+        context
+            .conditions
+            .iter()
+            .map(|(cfg, triples)| {
+                (
+                    cfg.clone(),
+                    triples
+                        .iter()
+                        .map(|triple| {
+                            render_platform_constraint_label(
+                                &self.config.platforms_template,
+                                triple,
+                            )
+                        })
+                        .collect(),
+                )
+            })
+            .collect()
+    }
+
     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")?;
@@ -74,7 +105,7 @@
 
         // Banner comment for top of the file.
         let header = self.engine.render_header()?;
-        starlark.push(Starlark::Comment(header));
+        starlark.push(Starlark::Verbatim(header));
 
         // Package visibility, exported bzl files.
         let package = Package::default_visibility_public();
@@ -114,14 +145,14 @@
                     } else {
                         rename.clone()
                     },
-                    actual: self.crate_label(krate, library_target_name),
+                    actual: self.crate_label(&krate.name, &krate.version, library_target_name),
                     tags: BTreeSet::from(["manual".to_owned()]),
                 });
             }
         }
         if !dependencies.is_empty() {
             let comment = "# Workspace Member Dependencies".to_owned();
-            starlark.push(Starlark::Comment(comment));
+            starlark.push(Starlark::Verbatim(comment));
             starlark.extend(dependencies.into_iter().map(Starlark::Alias));
         }
 
@@ -138,7 +169,11 @@
                         } else {
                             format!("{}__{}", krate.name, bin.crate_name)
                         },
-                        actual: self.crate_label(krate, &format!("{}__bin", bin.crate_name)),
+                        actual: self.crate_label(
+                            &krate.name,
+                            &krate.version,
+                            &format!("{}__bin", bin.crate_name),
+                        ),
                         tags: BTreeSet::from(["manual".to_owned()]),
                     });
                 }
@@ -146,7 +181,7 @@
         }
         if !binaries.is_empty() {
             let comment = "# Binaries".to_owned();
-            starlark.push(Starlark::Comment(comment));
+            starlark.push(Starlark::Verbatim(comment));
             starlark.extend(binaries.into_iter().map(Starlark::Alias));
         }
 
@@ -154,33 +189,386 @@
         Ok(starlark)
     }
 
-    fn render_build_files(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
+    fn render_build_files(
+        &self,
+        context: &Context,
+        platforms: &Platforms,
+    ) -> Result<BTreeMap<PathBuf, String>> {
         let default_splicing_package_id = default_splicing_package_crate_id();
-        self.engine
-            .render_crate_build_files(context)?
-            .into_iter()
+        context
+            .crates
+            .keys()
             // Do not render the default splicing package
-            .filter(|(id, _)| *id != &default_splicing_package_id)
+            .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];
+            .filter(|id| !context.workspace_members.contains_key(id))
+            .map(|id| {
                 let label = match render_build_file_template(
                     &self.config.build_file_template,
-                    &ctx.name,
-                    &ctx.version,
+                    &id.name,
+                    &id.version,
                 ) {
                     Ok(label) => label,
                     Err(e) => bail!(e),
                 };
 
                 let filename = Renderer::label_to_path(&label);
-
+                let content = self.render_one_build_file(platforms, &context.crates[id])?;
                 Ok((filename, content))
             })
             .collect()
     }
 
+    fn render_one_build_file(&self, platforms: &Platforms, krate: &CrateContext) -> Result<String> {
+        let mut starlark = Vec::new();
+
+        // Banner comment for top of the file.
+        let header = self.engine.render_header()?;
+        starlark.push(Starlark::Verbatim(header));
+
+        // Loads.
+        starlark.push(Starlark::Load(Load {
+            bzl: "@bazel_skylib//lib:dicts.bzl".to_owned(),
+            items: BTreeSet::from(["dicts".to_owned()]),
+        }));
+        starlark.push(Starlark::Load(Load {
+            bzl: "@rules_rust//cargo:defs.bzl".to_owned(),
+            items: BTreeSet::from(["cargo_build_script".to_owned()]),
+        }));
+        starlark.push(Starlark::Load(Load {
+            bzl: "@rules_rust//rust:defs.bzl".to_owned(),
+            items: BTreeSet::from([
+                "rust_binary".to_owned(),
+                "rust_library".to_owned(),
+                "rust_proc_macro".to_owned(),
+            ]),
+        }));
+        let disable_visibility = "# buildifier: disable=bzl-visibility".to_owned();
+        starlark.push(Starlark::Verbatim(disable_visibility));
+        starlark.push(Starlark::Load(Load {
+            bzl: "@rules_rust//crate_universe/private:selects.bzl".to_owned(),
+            items: BTreeSet::from(["selects".to_owned()]),
+        }));
+
+        // Package visibility.
+        let package = Package::default_visibility_public();
+        starlark.push(Starlark::Package(package));
+
+        if let Some(license) = &krate.license {
+            starlark.push(Starlark::Verbatim(formatdoc! {r#"
+                # licenses([
+                #     "TODO",  # {license}
+                # ])
+            "#}));
+        }
+
+        for rule in &krate.targets {
+            match rule {
+                Rule::BuildScript(target) => {
+                    let cargo_build_script =
+                        self.make_cargo_build_script(platforms, krate, target)?;
+                    starlark.push(Starlark::CargoBuildScript(cargo_build_script));
+                    starlark.push(Starlark::Alias(Alias {
+                        name: target.crate_name.clone(),
+                        actual: format!("{}_build_script", krate.name),
+                        tags: BTreeSet::from(["manual".to_owned()]),
+                    }));
+                }
+                Rule::ProcMacro(target) => {
+                    let rust_proc_macro = self.make_rust_proc_macro(platforms, krate, target)?;
+                    starlark.push(Starlark::RustProcMacro(rust_proc_macro));
+                }
+                Rule::Library(target) => {
+                    let rust_library = self.make_rust_library(platforms, krate, target)?;
+                    starlark.push(Starlark::RustLibrary(rust_library));
+                }
+                Rule::Binary(target) => {
+                    let rust_binary = self.make_rust_binary(platforms, krate, target)?;
+                    starlark.push(Starlark::RustBinary(rust_binary));
+                }
+            }
+        }
+
+        if let Some(additive_build_file_content) = &krate.additive_build_file_content {
+            let comment = "# Additive BUILD file content".to_owned();
+            starlark.push(Starlark::Verbatim(comment));
+            starlark.push(Starlark::Verbatim(additive_build_file_content.clone()));
+        }
+
+        let starlark = starlark::serialize(&starlark)?;
+        Ok(starlark)
+    }
+
+    fn make_cargo_build_script(
+        &self,
+        platforms: &Platforms,
+        krate: &CrateContext,
+        target: &TargetAttributes,
+    ) -> Result<CargoBuildScript> {
+        let empty_set = BTreeSet::<String>::new();
+        let empty_list = SelectList::<String>::default();
+        let empty_deps = SelectList::<CrateDependency>::default();
+        let attrs = krate.build_script_attrs.as_ref();
+
+        Ok(CargoBuildScript {
+            // Because `cargo_build_script` does some invisible target name
+            // mutating to determine the package and crate name for a build
+            // script, the Bazel target name of any build script cannot be the
+            // Cargo canonical name of "cargo_build_script" without losing out
+            // on having certain Cargo environment variables set.
+            //
+            // Do not change this name to "cargo_build_script".
+            name: format!("{}_build_script", krate.name),
+            aliases: self
+                .make_aliases(krate, true, false)
+                .remap_configurations(platforms),
+            build_script_env: attrs
+                .map_or_else(SelectDict::default, |attrs| attrs.build_script_env.clone())
+                .remap_configurations(platforms),
+            compile_data: make_data(
+                platforms,
+                &empty_set,
+                attrs.map_or(&empty_list, |attrs| &attrs.compile_data),
+            ),
+            crate_features: krate.common_attrs.crate_features.clone(),
+            crate_name: utils::sanitize_module_name(&target.crate_name),
+            crate_root: target.crate_root.clone(),
+            data: make_data(
+                platforms,
+                attrs.map_or(&empty_set, |attrs| &attrs.data_glob),
+                attrs.map_or(&empty_list, |attrs| &attrs.data),
+            ),
+            deps: self
+                .make_deps(
+                    attrs.map_or(&empty_deps, |attrs| &attrs.deps),
+                    attrs.map_or(&empty_set, |attrs| &attrs.extra_deps),
+                )
+                .remap_configurations(platforms),
+            edition: krate.common_attrs.edition.clone(),
+            linker_script: krate.common_attrs.linker_script.clone(),
+            links: attrs.and_then(|attrs| attrs.links.clone()),
+            proc_macro_deps: self
+                .make_deps(
+                    attrs.map_or(&empty_deps, |attrs| &attrs.proc_macro_deps),
+                    attrs.map_or(&empty_set, |attrs| &attrs.extra_proc_macro_deps),
+                )
+                .remap_configurations(platforms),
+            rustc_env: attrs
+                .map_or_else(SelectDict::default, |attrs| attrs.rustc_env.clone())
+                .remap_configurations(platforms),
+            rustc_env_files: attrs
+                .map_or_else(SelectList::default, |attrs| attrs.rustc_env_files.clone())
+                .remap_configurations(platforms),
+            rustc_flags: {
+                let mut rustc_flags =
+                    attrs.map_or_else(SelectList::default, |attrs| attrs.rustc_flags.clone());
+                // In most cases, warnings in 3rd party crates are not
+                // interesting as they're out of the control of consumers. The
+                // flag here silences warnings. For more details see:
+                // https://doc.rust-lang.org/rustc/lints/levels.html
+                rustc_flags.insert("--cap-lints=allow".to_owned(), None);
+                rustc_flags.remap_configurations(platforms)
+            },
+            srcs: target.srcs.clone(),
+            tags: {
+                let mut tags = BTreeSet::from_iter(krate.common_attrs.tags.iter().cloned());
+                tags.insert("cargo-bazel".to_owned());
+                tags.insert("manual".to_owned());
+                tags.insert("noclippy".to_owned());
+                tags.insert("norustfmt".to_owned());
+                tags
+            },
+            tools: attrs
+                .map_or_else(SelectList::default, |attrs| attrs.tools.clone())
+                .remap_configurations(platforms),
+            toolchains: attrs.map_or_else(BTreeSet::new, |attrs| attrs.toolchains.clone()),
+            version: krate.common_attrs.version.clone(),
+            visibility: BTreeSet::from(["//visibility:private".to_owned()]),
+        })
+    }
+
+    fn make_rust_proc_macro(
+        &self,
+        platforms: &Platforms,
+        krate: &CrateContext,
+        target: &TargetAttributes,
+    ) -> Result<RustProcMacro> {
+        Ok(RustProcMacro {
+            name: target.crate_name.clone(),
+            deps: self
+                .make_deps(&krate.common_attrs.deps, &krate.common_attrs.extra_deps)
+                .remap_configurations(platforms),
+            proc_macro_deps: self
+                .make_deps(
+                    &krate.common_attrs.proc_macro_deps,
+                    &krate.common_attrs.extra_proc_macro_deps,
+                )
+                .remap_configurations(platforms),
+            aliases: self
+                .make_aliases(krate, false, false)
+                .remap_configurations(platforms),
+            common: self.make_common_attrs(platforms, krate, target)?,
+        })
+    }
+
+    fn make_rust_library(
+        &self,
+        platforms: &Platforms,
+        krate: &CrateContext,
+        target: &TargetAttributes,
+    ) -> Result<RustLibrary> {
+        Ok(RustLibrary {
+            name: target.crate_name.clone(),
+            deps: self
+                .make_deps(&krate.common_attrs.deps, &krate.common_attrs.extra_deps)
+                .remap_configurations(platforms),
+            proc_macro_deps: self
+                .make_deps(
+                    &krate.common_attrs.proc_macro_deps,
+                    &krate.common_attrs.extra_proc_macro_deps,
+                )
+                .remap_configurations(platforms),
+            aliases: self
+                .make_aliases(krate, false, false)
+                .remap_configurations(platforms),
+            common: self.make_common_attrs(platforms, krate, target)?,
+        })
+    }
+
+    fn make_rust_binary(
+        &self,
+        platforms: &Platforms,
+        krate: &CrateContext,
+        target: &TargetAttributes,
+    ) -> Result<RustBinary> {
+        Ok(RustBinary {
+            name: format!("{}__bin", target.crate_name),
+            deps: {
+                let mut deps =
+                    self.make_deps(&krate.common_attrs.deps, &krate.common_attrs.extra_deps);
+                if let Some(library_target_name) = &krate.library_target_name {
+                    deps.insert(format!(":{library_target_name}"), None);
+                }
+                deps.remap_configurations(platforms)
+            },
+            proc_macro_deps: self
+                .make_deps(
+                    &krate.common_attrs.proc_macro_deps,
+                    &krate.common_attrs.extra_proc_macro_deps,
+                )
+                .remap_configurations(platforms),
+            aliases: self
+                .make_aliases(krate, false, false)
+                .remap_configurations(platforms),
+            common: self.make_common_attrs(platforms, krate, target)?,
+        })
+    }
+
+    fn make_common_attrs(
+        &self,
+        platforms: &Platforms,
+        krate: &CrateContext,
+        target: &TargetAttributes,
+    ) -> Result<CommonAttrs> {
+        Ok(CommonAttrs {
+            compile_data: make_data(
+                platforms,
+                &krate.common_attrs.compile_data_glob,
+                &krate.common_attrs.compile_data,
+            ),
+            crate_features: krate.common_attrs.crate_features.clone(),
+            crate_root: target.crate_root.clone(),
+            data: make_data(
+                platforms,
+                &krate.common_attrs.data_glob,
+                &krate.common_attrs.data,
+            ),
+            edition: krate.common_attrs.edition.clone(),
+            linker_script: krate.common_attrs.linker_script.clone(),
+            rustc_env: krate
+                .common_attrs
+                .rustc_env
+                .clone()
+                .remap_configurations(platforms),
+            rustc_env_files: krate
+                .common_attrs
+                .rustc_env_files
+                .clone()
+                .remap_configurations(platforms),
+            rustc_flags: {
+                let mut rustc_flags = krate.common_attrs.rustc_flags.clone();
+                // In most cases, warnings in 3rd party crates are not
+                // interesting as they're out of the control of consumers. The
+                // flag here silences warnings. For more details see:
+                // https://doc.rust-lang.org/rustc/lints/levels.html
+                rustc_flags.insert(0, "--cap-lints=allow".to_owned());
+                rustc_flags
+            },
+            srcs: target.srcs.clone(),
+            tags: {
+                let mut tags = BTreeSet::from_iter(krate.common_attrs.tags.iter().cloned());
+                tags.insert("cargo-bazel".to_owned());
+                tags.insert("manual".to_owned());
+                tags.insert("noclippy".to_owned());
+                tags.insert("norustfmt".to_owned());
+                tags
+            },
+            version: krate.common_attrs.version.clone(),
+        })
+    }
+
+    /// Filter a crate's dependencies to only ones with aliases
+    fn make_aliases(
+        &self,
+        krate: &CrateContext,
+        build: bool,
+        include_dev: bool,
+    ) -> SelectDict<String> {
+        let mut dep_lists = Vec::new();
+        if build {
+            if let Some(build_script_attrs) = &krate.build_script_attrs {
+                dep_lists.push(&build_script_attrs.deps);
+                dep_lists.push(&build_script_attrs.proc_macro_deps);
+            }
+        } else {
+            dep_lists.push(&krate.common_attrs.deps);
+            dep_lists.push(&krate.common_attrs.proc_macro_deps);
+            if include_dev {
+                dep_lists.push(&krate.common_attrs.deps_dev);
+                dep_lists.push(&krate.common_attrs.proc_macro_deps_dev);
+            }
+        }
+
+        let mut aliases = SelectDict::default();
+        for (dep, conf) in dep_lists.into_iter().flat_map(|deps| {
+            deps.configurations().into_iter().flat_map(move |conf| {
+                deps.get_iter(conf)
+                    .expect("Iterating over known keys should never panic")
+                    .map(move |dep| (dep, conf))
+            })
+        }) {
+            if let Some(alias) = &dep.alias {
+                let label = self.crate_label(&dep.id.name, &dep.id.version, &dep.target);
+                aliases.insert(label, alias.clone(), conf.cloned());
+            }
+        }
+        aliases
+    }
+
+    fn make_deps(
+        &self,
+        deps: &SelectList<CrateDependency>,
+        extra_deps: &BTreeSet<String>,
+    ) -> SelectList<String> {
+        let mut deps = deps
+            .clone()
+            .map(|dep| self.crate_label(&dep.id.name, &dep.id.version, &dep.target));
+        for extra_dep in extra_deps {
+            deps.insert(extra_dep.clone(), None);
+        }
+        deps
+    }
+
     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")?;
@@ -201,12 +589,12 @@
         }
     }
 
-    fn crate_label(&self, krate: &CrateContext, target: &str) -> String {
+    fn crate_label(&self, name: &str, version: &str, target: &str) -> String {
         sanitize_repository_name(&render_crate_bazel_label(
             &self.config.crate_label_template,
             &self.config.repository_name,
-            &krate.name,
-            &krate.version,
+            name,
+            version,
             target,
         ))
     }
@@ -291,7 +679,7 @@
 }
 
 /// Render the Bazel label of a platform triple
-pub fn render_platform_constraint_label(template: &str, triple: &str) -> String {
+fn render_platform_constraint_label(template: &str, triple: &str) -> String {
     template.replace("{triple}", triple)
 }
 
@@ -303,10 +691,33 @@
     )
 }
 
+fn make_data(platforms: &Platforms, glob: &BTreeSet<String>, select: &SelectList<String>) -> Data {
+    const COMMON_GLOB_EXCLUDES: &[&str] = &[
+        "**/* *",
+        "BUILD.bazel",
+        "BUILD",
+        "WORKSPACE.bazel",
+        "WORKSPACE",
+    ];
+
+    Data {
+        glob: Glob {
+            include: glob.clone(),
+            exclude: COMMON_GLOB_EXCLUDES
+                .iter()
+                .map(|&glob| glob.to_owned())
+                .collect(),
+        },
+        select: select.clone().remap_configurations(platforms),
+    }
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
 
+    use indoc::indoc;
+
     use crate::config::{Config, CrateId, VendorMode};
     use crate::context::crate_context::{CrateContext, Rule};
     use crate::context::{BuildScriptAttributes, CommonAttributes, Context, TargetAttributes};
@@ -649,19 +1060,22 @@
 
         // This is unfortunately somewhat brittle. Alas. Ultimately we wish to demonstrate that the
         // original cfg(...) strings are preserved in the `deps` list for ease of debugging.
+        let expected = indoc! {r#"
+            deps = select({
+                "@rules_rust//rust/platform:aarch64-apple-darwin": [
+                    "@multi_cfg_dep__libc-0.2.117//:libc",  # aarch64-apple-darwin
+                ],
+                "@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [
+                    "@multi_cfg_dep__libc-0.2.117//:libc",  # cfg(all(target_arch = "aarch64", target_os = "linux"))
+                ],
+                "//conditions:default": [],
+            }),
+        "#};
+
         assert!(
-            build_file_content.replace(' ', "")
-            .contains(&
-r#"deps = [
-    ] + select({
-        "@rules_rust//rust/platform:aarch64-apple-darwin": [
-            "@multi_cfg_dep__libc-0.2.117//:libc",  # aarch64-apple-darwin
-        ],
-        "@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [
-            "@multi_cfg_dep__libc-0.2.117//:libc",  # cfg(all(target_arch = "aarch64", target_os = "linux"))
-        ],
-        "//conditions:default": [
-        ],
-    }),"#.replace(' ', "")));
+            build_file_content.contains(&expected.replace('\n', "\n    ")),
+            "{}",
+            build_file_content,
+        );
     }
 }
diff --git a/crate_universe/src/rendering/template_engine.rs b/crate_universe/src/rendering/template_engine.rs
index 0006783..1208823 100644
--- a/crate_universe/src/rendering/template_engine.rs
+++ b/crate_universe/src/rendering/template_engine.rs
@@ -1,90 +1,30 @@
 //! A template engine backed by [Tera] for rendering Files.
 
-use std::collections::{BTreeMap, BTreeSet, HashMap};
+use std::collections::HashMap;
 
 use anyhow::{Context as AnyhowContext, Result};
-use serde::{Deserialize, Serialize};
 use serde_json::{from_value, to_value, Value};
 use tera::{self, Tera};
 
-use crate::config::{CrateId, RenderConfig};
-use crate::context::crate_context::CrateDependency;
+use crate::config::RenderConfig;
 use crate::context::Context;
 use crate::rendering::{
     render_crate_bazel_label, render_crate_bazel_repository, render_crate_build_file,
-    render_module_label, render_platform_constraint_label,
+    render_module_label,
 };
-use crate::utils::sanitize_module_name;
 use crate::utils::sanitize_repository_name;
-use crate::utils::starlark::{SelectList, SelectStringDict, SelectStringList};
+use crate::utils::starlark::SelectStringList;
 
 pub struct TemplateEngine {
     engine: Tera,
     context: tera::Context,
 }
 
-const COMMON_GLOB_EXCLUDES: &[&str] = &[
-    "**/* *",
-    "BUILD.bazel",
-    "BUILD",
-    "WORKSPACE.bazel",
-    "WORKSPACE",
-];
-
 impl TemplateEngine {
     pub fn new(render_config: &RenderConfig) -> Self {
         let mut tera = Tera::default();
         tera.add_raw_templates(vec![
             (
-                "partials/crate/aliases.j2",
-                include_str!(concat!(
-                    env!("CARGO_MANIFEST_DIR"),
-                    "/src/rendering/templates/partials/crate/aliases.j2"
-                )),
-            ),
-            (
-                "partials/crate/binary.j2",
-                include_str!(concat!(
-                    env!("CARGO_MANIFEST_DIR"),
-                    "/src/rendering/templates/partials/crate/binary.j2"
-                )),
-            ),
-            (
-                "partials/crate/build_script.j2",
-                include_str!(concat!(
-                    env!("CARGO_MANIFEST_DIR"),
-                    "/src/rendering/templates/partials/crate/build_script.j2"
-                )),
-            ),
-            (
-                "partials/crate/common_attrs.j2",
-                include_str!(concat!(
-                    env!("CARGO_MANIFEST_DIR"),
-                    "/src/rendering/templates/partials/crate/common_attrs.j2"
-                )),
-            ),
-            (
-                "partials/crate/deps.j2",
-                include_str!(concat!(
-                    env!("CARGO_MANIFEST_DIR"),
-                    "/src/rendering/templates/partials/crate/deps.j2"
-                )),
-            ),
-            (
-                "partials/crate/library.j2",
-                include_str!(concat!(
-                    env!("CARGO_MANIFEST_DIR"),
-                    "/src/rendering/templates/partials/crate/library.j2"
-                )),
-            ),
-            (
-                "partials/crate/proc_macro.j2",
-                include_str!(concat!(
-                    env!("CARGO_MANIFEST_DIR"),
-                    "/src/rendering/templates/partials/crate/proc_macro.j2"
-                )),
-            ),
-            (
                 "partials/module/aliases_map.j2",
                 include_str!(concat!(
                     env!("CARGO_MANIFEST_DIR"),
@@ -113,27 +53,6 @@
                 )),
             ),
             (
-                "partials/starlark/glob.j2",
-                include_str!(concat!(
-                    env!("CARGO_MANIFEST_DIR"),
-                    "/src/rendering/templates/partials/starlark/glob.j2"
-                )),
-            ),
-            (
-                "partials/starlark/selectable_dict.j2",
-                include_str!(concat!(
-                    env!("CARGO_MANIFEST_DIR"),
-                    "/src/rendering/templates/partials/starlark/selectable_dict.j2"
-                )),
-            ),
-            (
-                "partials/starlark/selectable_list.j2",
-                include_str!(concat!(
-                    env!("CARGO_MANIFEST_DIR"),
-                    "/src/rendering/templates/partials/starlark/selectable_list.j2"
-                )),
-            ),
-            (
                 "partials/header.j2",
                 include_str!(concat!(
                     env!("CARGO_MANIFEST_DIR"),
@@ -141,13 +60,6 @@
                 )),
             ),
             (
-                "crate_build_file.j2",
-                include_str!(concat!(
-                    env!("CARGO_MANIFEST_DIR"),
-                    "/src/rendering/templates/crate_build_file.j2"
-                )),
-            ),
-            (
                 "module_bzl.j2",
                 include_str!(concat!(
                     env!("CARGO_MANIFEST_DIR"),
@@ -183,27 +95,16 @@
             ),
         );
         tera.register_function(
-            "platform_label",
-            platform_label_fn_generator(render_config.platforms_template.clone()),
-        );
-        tera.register_function("sanitize_module_name", sanitize_module_name_fn);
-        tera.register_function(
             "crates_module_label",
             module_label_fn_generator(render_config.crates_module_template.clone()),
         );
-        tera.register_filter(
-            "remap_deps_configurations",
-            remap_select_list_configurations::<CrateDependency>,
-        );
 
         let mut context = tera::Context::new();
         context.insert("default_select_list", &SelectStringList::default());
-        context.insert("default_select_dict", &SelectStringDict::default());
         context.insert("repository_name", &render_config.repository_name);
         context.insert("vendor_mode", &render_config.vendor_mode);
         context.insert("regen_command", &render_config.regen_command);
         context.insert("Null", &tera::Value::Null);
-        context.insert("common_glob_excludes", &COMMON_GLOB_EXCLUDES);
         context.insert(
             "default_package_name",
             &match render_config.default_package_name.as_ref() {
@@ -232,35 +133,6 @@
         Ok(header)
     }
 
-    pub fn render_crate_build_files<'a>(
-        &self,
-        ctx: &'a Context,
-    ) -> Result<BTreeMap<&'a CrateId, String>> {
-        // Create the render context with the global planned context to be
-        // reused when rendering crates.
-        let mut context = self.new_tera_ctx();
-        context.insert("context", ctx);
-
-        ctx.crates
-            .keys()
-            .map(|id| {
-                let aliases = ctx.crate_aliases(id, false, false);
-                let build_aliases = ctx.crate_aliases(id, true, false);
-
-                context.insert("crate_id", &id);
-                context.insert("common_aliases", &aliases);
-                context.insert("build_aliases", &build_aliases);
-
-                let content = self
-                    .engine
-                    .render("crate_build_file.j2", &context)
-                    .context("Failed to render BUILD file")?;
-
-                Ok((id, content))
-            })
-            .collect()
-    }
-
     pub fn render_module_bzl(&self, data: &Context) -> Result<String> {
         let mut context = self.new_tera_ctx();
         context.insert("context", data);
@@ -304,29 +176,6 @@
 }
 
 /// Convert a crate name into a module name by applying transforms to invalid characters.
-fn sanitize_module_name_fn(args: &HashMap<String, Value>) -> tera::Result<Value> {
-    let crate_name = parse_tera_param!("crate_name", String, args);
-
-    match to_value(sanitize_module_name(&crate_name)) {
-        Ok(v) => Ok(v),
-        Err(_) => Err(tera::Error::msg("Failed to generate resulting module name")),
-    }
-}
-
-/// Convert a crate name into a module name by applying transforms to invalid characters.
-fn platform_label_fn_generator(template: String) -> impl tera::Function {
-    Box::new(
-        move |args: &HashMap<String, Value>| -> tera::Result<Value> {
-            let triple = parse_tera_param!("triple", String, args);
-            match to_value(render_platform_constraint_label(&template, &triple)) {
-                Ok(v) => Ok(v),
-                Err(_) => Err(tera::Error::msg("Failed to generate resulting module name")),
-            }
-        },
-    )
-}
-
-/// Convert a crate name into a module name by applying transforms to invalid characters.
 fn crate_build_file_fn_generator(template: String) -> impl tera::Function {
     Box::new(
         move |args: &HashMap<String, Value>| -> tera::Result<Value> {
@@ -401,20 +250,3 @@
         },
     )
 }
-
-/// Tera filter which re-keys a [`SelectList`]. See [`SelectList::remap_configurations`]
-/// for more details. The mapping must be provided as a Tera argument named `mapping`.
-fn remap_select_list_configurations<T: Ord + Clone + for<'a> Deserialize<'a> + Serialize>(
-    input: &Value,
-    args: &HashMap<String, Value>,
-) -> tera::Result<Value> {
-    let mapping = parse_tera_param!("mapping", BTreeMap<String, BTreeSet<String>>, args);
-
-    match from_value::<SelectList<T>>(input.clone()) {
-        Ok(v) => match to_value(v.remap_configurations(&mapping)) {
-            Ok(v) => Ok(v),
-            Err(_) => Err(tera::Error::msg("Failed to remap conditions.")),
-        },
-        Err(_) => Err(tera::Error::msg("The filter input could not be parsed.")),
-    }
-}
diff --git a/crate_universe/src/rendering/templates/crate_build_file.j2 b/crate_universe/src/rendering/templates/crate_build_file.j2
deleted file mode 100644
index ff9d4ad..0000000
--- a/crate_universe/src/rendering/templates/crate_build_file.j2
+++ /dev/null
@@ -1,44 +0,0 @@
-{%- set crate = context.crates | get(key=crate_id) %}
-{%- include "partials/header.j2" %}
-
-load(
-    "@bazel_skylib//lib:selects.bzl", 
-    "selects",
-)
-load(
-    "@rules_rust//cargo:defs.bzl",
-    "cargo_build_script",
-)
-load(
-    "@rules_rust//rust:defs.bzl",
-    "rust_binary",
-    "rust_library",
-    "rust_proc_macro",
-)
-
-# buildifier: disable=bzl-visibility
-load("@rules_rust//crate_universe/private:selects.bzl", "select_with_or")
-
-package(default_visibility = ["//visibility:public"])
-
-# licenses([
-#     "TODO",  # {{ crate.license }}
-# ])
-
-{% for rule in crate.targets -%}
-{%- for rule_type, target in rule %}
-{%- if rule_type in ["BuildScript"] %}
-{% include "partials/crate/build_script.j2" %}
-{%- elif rule_type in ["ProcMacro"] %}
-{% include "partials/crate/proc_macro.j2" %}
-{%- elif rule_type in ["Library"] %}
-{% include "partials/crate/library.j2" %}
-{%- elif rule_type in ["Binary"] %}
-{% include "partials/crate/binary.j2" %}
-{%- endif %}
-{%- endfor %}
-{%- endfor %}
-{%- if crate.additive_build_file_content %}
-# Additive BUILD file content
-{{ crate.additive_build_file_content }}
-{%- endif %}
diff --git a/crate_universe/src/rendering/templates/partials/crate/aliases.j2 b/crate_universe/src/rendering/templates/partials/crate/aliases.j2
deleted file mode 100644
index 931abe0..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/aliases.j2
+++ /dev/null
@@ -1,23 +0,0 @@
-select({
-    {%- set selectable_and_unmapped = selectable | remap_deps_configurations(mapping=context.conditions) %}
-    {%- set selectable = selectable_and_unmapped.0 %}
-    {%- set unmapped = selectable_and_unmapped.1 %}
-    {%- for triple, deps in selectable.selects %}
-        "{{ platform_label(triple = triple) }}": {
-            {%- for dep in deps %}
-            {%- set_global orig_confg_list = [] %}
-            {%- for orig_config in dep.original_configurations %}
-            {%- set_global orig_confg_list = orig_confg_list | concat(with=orig_config | default(value="common dependency")) %}
-            {%- endfor -%}
-            {%- set dep_crate = context.crates | get(key=dep.value.id) %}
-            "{{ crate_label(name = dep_crate.name, version = dep_crate.version, target = dep.value.target) }}": "{{ dep.value.alias }}",  # {{ orig_confg_list | join(sep=", ") }}
-            {%- endfor %}
-        },
-    {%- endfor %}
-        "//conditions:default": {
-            {%- for dep in selectable.common %}
-            {%- set dep_crate = context.crates | get(key=dep.value.id) %}
-            "{{ crate_label(name = dep_crate.name, version = dep_crate.version, target = dep.value.target) }}": "{{ dep.value.alias }}",
-            {%- endfor %}
-        },
-    })
\ No newline at end of file
diff --git a/crate_universe/src/rendering/templates/partials/crate/binary.j2 b/crate_universe/src/rendering/templates/partials/crate/binary.j2
deleted file mode 100644
index 2cdc5d9..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/binary.j2
+++ /dev/null
@@ -1,18 +0,0 @@
-rust_binary(
-    name = "{{ target.crate_name }}__bin",
-    deps = [
-        {%- if crate.library_target_name %}
-        ":{{ crate.library_target_name }}",
-        {%- endif %}
-        {%- for dep in crate.common_attrs | get(key="extra_deps", default=[]) %}
-        "{{ dep }}",
-        {%- endfor %}
-    ] + {% set deps = crate.common_attrs | get(key="deps", default=Null) %}{% include "partials/crate/deps.j2" %},
-    proc_macro_deps = [
-        {%- for dep in crate.common_attrs | get(key="extra_proc_macro_deps", default=[]) %}
-        "{{ dep }}",
-        {%- endfor %}
-    ] + {% set deps = crate.common_attrs | get(key="proc_macro_deps", default=Null) %}{% include "partials/crate/deps.j2" %},
-    aliases = {% set selectable = common_aliases %}{% include "partials/crate/aliases.j2" -%},
-{% include "partials/crate/common_attrs.j2" %}
-)
diff --git a/crate_universe/src/rendering/templates/partials/crate/build_script.j2 b/crate_universe/src/rendering/templates/partials/crate/build_script.j2
deleted file mode 100644
index c806334..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/build_script.j2
+++ /dev/null
@@ -1,84 +0,0 @@
-cargo_build_script(
-    # See comment associated with alias. Do not change this name
-    name = "{{ crate.name }}_build_script",
-    aliases = {% set selectable = build_aliases %}{% include "partials/crate/aliases.j2" -%},
-    build_script_env = {% set selectable = crate.build_script_attrs | get(key="build_script_env", default=Null) %}{% include "partials/starlark/selectable_dict.j2" -%},
-    compile_data = {% if crate.build_script_attrs | get(key="compile_data_glob") %}glob(include = {{ crate.build_script_attrs.compile_data_glob | json_encode | safe }}, exclude = {{ common_glob_excludes | json_encode() | safe }}) + {% endif %}{% set selectable = crate.build_script_attrs | get(key="compile_data", default=Null) %}{% include "partials/starlark/selectable_list.j2" %},
-    crate_name = "{{ sanitize_module_name(crate_name=target.crate_name) }}",
-    crate_root = "{{ target.crate_root }}",
-    crate_features = [
-        {%- if crate.common_attrs | get(key="crate_features", default=Null) %}
-        {%- for feature in crate.common_attrs.crate_features %}
-        "{{ feature }}",
-        {%- endfor %}
-        {%- endif %}
-    ],
-    data = {% if crate.build_script_attrs | get(key="data_glob") %}glob(include = {{ crate.build_script_attrs.data_glob | json_encode | safe }}, exclude = {{ common_glob_excludes | json_encode() | safe }}) + {% endif %}{% set selectable = crate.build_script_attrs | get(key="data", default=Null) %}{% include "partials/starlark/selectable_list.j2" %},
-    deps = [
-        {%- for dep in crate.build_script_attrs | get(key="extra_deps", default=[]) %}
-        "{{ dep }}",
-        {%- endfor %}
-    ] + {% set deps = crate.build_script_attrs | get(key="deps", default=Null) %}{% include "partials/crate/deps.j2" %},
-    edition = "{{ crate.common_attrs.edition }}",
-    {%- if crate.common_attrs.linker_script %}
-    linker_script = "{{ crate.common_attrs.linker_script }}",
-    {%- endif %}
-    {%- if crate.build_script_attrs | get(key="links", default=Null) %}
-    links = "{{ crate.build_script_attrs.links }}",
-    {%- endif %}
-    proc_macro_deps = [
-        {%- for dep in crate.build_script_attrs | get(key="extra_proc_macro_deps", default=[]) %}
-        "{{ dep }}",
-        {%- endfor %}
-    ] + {% set deps = crate.build_script_attrs | get(key="proc_macro_deps", default=Null) %}{% include "partials/crate/deps.j2" %},
-    rustc_env = {% set selectable = crate.build_script_attrs | get(key="rustc_env", default=Null) %}{% include "partials/starlark/selectable_dict.j2" -%},
-    rustc_env_files = {% set selectable = crate.build_script_attrs | get(key="rustc_env_files", default=Null) %}{% include "partials/starlark/selectable_list.j2" %},
-    rustc_flags = [
-        # In most cases, warnings in 3rd party crates are not interesting as
-        # they're out of the control of consumers. The flag here silences 
-        # warnings. For more details see: 
-        # https://doc.rust-lang.org/rustc/lints/levels.html
-        "--cap-lints=allow",
-        {%- if crate.common_attrs | get(key="rustc_flags", default=Null) %}
-
-        # User provided rustc_flags
-        {%- for rustc_flag in crate.common_attrs.rustc_flags %}
-        "{{ rustc_flag }}",
-        {%- endfor %}
-        {%- endif %}
-    ],
-    srcs = {% set glob = target.srcs %}{% include "partials/starlark/glob.j2" -%},
-    tools = {% set selectable = crate.build_script_attrs | get(key="tools", default=Null) %}{% include "partials/starlark/selectable_list.j2" %},
-    version = "{{ crate.common_attrs.version }}",
-    tags = [
-        {%- if crate.common_attrs | get(key="tags", default=Null) %}
-        {%- for tag in crate.common_attrs.tags %}
-        "{{ tag }}",
-        {%- endfor %}
-        {%- endif %}
-        "cargo-bazel",
-        "manual",
-        "noclippy",
-        "norustfmt",
-    ],
-    {%- if crate.build_script_attrs | get(key="toolchains", default=Null) %}
-    toolchains = [
-        {%- for toolchain in crate.build_script_attrs.toolchains %}
-        "{{ toolchain }}",
-        {%- endfor %}
-    ],
-    {%- endif %}
-    visibility = ["//visibility:private"],
-)
-alias(
-    # Because `cargo_build_script` does some invisible target name mutating to
-    # determine the package and crate name for a build script, the Bazel
-    # target name of any build script cannot be the Cargo canonical name
-    # of `cargo_build_script` (rule) without losing out on having certain
-    # Cargo environment variables set.
-    name = "{{ target.crate_name }}",
-    actual = "{{ crate.name }}_build_script",
-    tags = [
-        "manual",
-    ],
-)
diff --git a/crate_universe/src/rendering/templates/partials/crate/common_attrs.j2 b/crate_universe/src/rendering/templates/partials/crate/common_attrs.j2
deleted file mode 100644
index efa5a97..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/common_attrs.j2
+++ /dev/null
@@ -1,41 +0,0 @@
-    compile_data = {% if crate.common_attrs | get(key="compile_data_glob") %}glob(include = {{ crate.common_attrs.compile_data_glob | json_encode | safe }}, exclude = {{ common_glob_excludes | json_encode() | safe }}) + {% endif %}{% set selectable = crate.common_attrs | get(key="compile_data", default=default_select_list) %}{% include "partials/starlark/selectable_list.j2" -%},
-    crate_root = "{{ target.crate_root }}",
-    crate_features = [
-        {%- for feature in crate.common_attrs | get(key="crate_features", default=[]) %}
-        "{{ feature }}",
-        {%- endfor %}
-    ],
-    data = {% if crate.common_attrs | get(key="data_glob") %}glob(include = {{ crate.common_attrs.data_glob | json_encode | safe }}, exclude = {{ common_glob_excludes | json_encode() | safe }}) + {% endif %}{% set selectable = crate.common_attrs | get(key="data", default=default_select_list) %}{% include "partials/starlark/selectable_list.j2" -%},
-    edition = "{{ crate.common_attrs.edition }}",
-    {%- if crate.common_attrs | get(key="linker_script", default=Null) %}
-    linker_script = "{{ crate.common_attrs.linker_script }}",
-    {%- endif %}
-    rustc_env = {% set selectable = crate.common_attrs | get(key="rustc_env", default=Null) %}{% include "partials/starlark/selectable_dict.j2" -%},
-    rustc_env_files = {% set selectable = crate.common_attrs | get(key="rustc_env_files", default=Null) %}{% include "partials/starlark/selectable_list.j2" -%},
-    rustc_flags = [
-        # In most cases, warnings in 3rd party crates are not interesting as
-        # they're out of the control of consumers. The flag here silences 
-        # warnings. For more details see: 
-        # https://doc.rust-lang.org/rustc/lints/levels.html
-        "--cap-lints=allow",
-        {%- if crate.common_attrs | get(key="rustc_flags", default=Null) %}
-
-        # User provided rustc_flags
-        {%- for rustc_flag in crate.common_attrs.rustc_flags %}
-        "{{ rustc_flag }}",
-        {%- endfor %}
-        {%- endif %}
-    ],
-    srcs = {% set glob = target.srcs %}{% include "partials/starlark/glob.j2" -%},
-    version = "{{ crate.common_attrs.version }}",
-    tags = [
-        {%- if crate.common_attrs | get(key="tags", default=Null) %}
-        {%- for tag in crate.common_attrs.tags %}
-        "{{ tag }}",
-        {%- endfor %}
-        {%- endif %}
-        "cargo-bazel",
-        "manual",
-        "noclippy",
-        "norustfmt",
-    ],
diff --git a/crate_universe/src/rendering/templates/partials/crate/deps.j2 b/crate_universe/src/rendering/templates/partials/crate/deps.j2
deleted file mode 100644
index 357d4c3..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/deps.j2
+++ /dev/null
@@ -1,29 +0,0 @@
-select({
-    {%- set selectable_and_unmapped = deps | default(value=default_select_list) | remap_deps_configurations(mapping=context.conditions) %}
-    {%- set selectable = selectable_and_unmapped.0 %}
-    {%- set unmapped = selectable_and_unmapped.1 %}
-    {%- for triple, deps in selectable.selects %}
-        "{{ platform_label(triple = triple) }}": [
-            {%- for dep in deps %}
-            {%- set_global orig_confg_list = [] %}
-            {%- for orig_config in dep.original_configurations %}
-            {%- set_global orig_confg_list = orig_confg_list | concat(with=orig_config | default(value="common dependency")) %}
-            {%- endfor -%}
-            {%- set dep_crate = context.crates | get(key=dep.value.id) %}
-            "{{ crate_label(name = dep_crate.name, version = dep_crate.version, target = dep.value.target) }}",  # {{ orig_confg_list | join(sep=", ") }}
-            {%- endfor %}
-        ],
-    {%- endfor %}
-        "//conditions:default": [
-            {%- for common_dep in selectable.common %}
-            {%- set common_dep_crate = context.crates | get(key=common_dep.value.id) %}
-            "{{ crate_label(name = common_dep_crate.name, version = common_dep_crate.version, target = common_dep.value.target) }}",
-            {%- endfor %}
-        ],
-    {%- for cfg, deps in unmapped %}
-        #
-        # No supported platform triples for cfg: '{{ cfg }}'
-        # Skipped dependencies: {{ deps | json_encode | safe }}
-        #
-    {%- endfor %}
-    })
\ No newline at end of file
diff --git a/crate_universe/src/rendering/templates/partials/crate/library.j2 b/crate_universe/src/rendering/templates/partials/crate/library.j2
deleted file mode 100644
index f678bd9..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/library.j2
+++ /dev/null
@@ -1,15 +0,0 @@
-rust_library(
-    name = "{{ target.crate_name }}",
-    deps = [
-        {%- for dep in crate.common_attrs | get(key="extra_deps", default=[]) %}
-        "{{ dep }}",
-        {%- endfor %}
-    ] + {% set deps = crate.common_attrs | get(key="deps", default=Null) %}{% include "partials/crate/deps.j2" %},
-    proc_macro_deps = [
-        {%- for dep in crate.common_attrs | get(key="extra_proc_macro_deps", default=[]) %}
-        "{{ dep }}",
-        {%- endfor %}
-    ] + {% set deps = crate.common_attrs | get(key="proc_macro_deps", default=Null) %}{% include "partials/crate/deps.j2" %},
-    aliases = {% set selectable = common_aliases %}{% include "partials/crate/aliases.j2" -%},
-{% include "partials/crate/common_attrs.j2" %}
-)
diff --git a/crate_universe/src/rendering/templates/partials/crate/proc_macro.j2 b/crate_universe/src/rendering/templates/partials/crate/proc_macro.j2
deleted file mode 100644
index c0b9d1d..0000000
--- a/crate_universe/src/rendering/templates/partials/crate/proc_macro.j2
+++ /dev/null
@@ -1,15 +0,0 @@
-rust_proc_macro(
-    name = "{{ target.crate_name }}",
-    deps = [
-        {%- for dep in crate.common_attrs | get(key="extra_deps", default=[]) %}
-        "{{ dep }}",
-        {%- endfor %}
-    ] + {% set deps = crate.common_attrs | get(key="deps", default=Null) %}{% include "partials/crate/deps.j2" %},
-    proc_macro_deps = [
-        {%- for dep in crate.common_attrs | get(key="extra_proc_macro_deps", default=[]) %}
-        "{{ dep }}",
-        {%- endfor %}
-    ] + {% set deps = crate.common_attrs | get(key="proc_macro_deps", default=Null) %}{% include "partials/crate/deps.j2" %},
-    aliases = {% set selectable = common_aliases %}{% include "partials/crate/aliases.j2" -%},
-{% include "partials/crate/common_attrs.j2" %}
-)
diff --git a/crate_universe/src/rendering/templates/partials/starlark/glob.j2 b/crate_universe/src/rendering/templates/partials/starlark/glob.j2
deleted file mode 100644
index 67f70af..0000000
--- a/crate_universe/src/rendering/templates/partials/starlark/glob.j2
+++ /dev/null
@@ -1,12 +0,0 @@
-glob(
-        include = [
-            {%- for pattern in glob.include %}
-            "{{ pattern }}",
-            {%- endfor %}
-        ],
-        exclude = [
-            {%- for pattern in glob.exclude %}
-            "{{ pattern }}",
-            {%- endfor %}
-        ],
-    )
\ No newline at end of file
diff --git a/crate_universe/src/rendering/templates/partials/starlark/selectable_dict.j2 b/crate_universe/src/rendering/templates/partials/starlark/selectable_dict.j2
deleted file mode 100644
index 8012cc7..0000000
--- a/crate_universe/src/rendering/templates/partials/starlark/selectable_dict.j2
+++ /dev/null
@@ -1,36 +0,0 @@
-{%- set selectable = selectable | default(value=default_select_dict) %}
-{%- if selectable.selects | length -%}
-    selects.with_or({
-    {%- for cfg, map in selectable.selects %}
-    {%- if cfg in context.conditions and context.conditions[cfg] | length %}
-        # {{ cfg }}
-        (
-            {%- for triple in context.conditions[cfg] %}
-            "{{ platform_label(triple = triple) }}",
-            {%- endfor %}
-        ): {
-            {%- if selectable.common | length %}
-            {%- for key, val in selectable.common %}
-            "{{ key }}": "{{ val }}",
-            {%- endfor %}
-            {%- endif %}
-            {%- for key, val in map %}
-            "{{ key }}": "{{ val }}",
-            {%- endfor %}
-        },
-    {%- else %}
-        # No supported platform triples for cfg: '{{ cfg }}'
-        # Skipped dependencies: {{ map | json_encode| safe }}
-    {%- endif %}
-    {%- endfor %}
-        "//conditions:default": {},
-    })
-{%- else -%}
-    {
-        {%- if selectable.common | length %}
-        {%- for key, val in selectable.common %}
-        "{{ key }}": "{{ val }}",
-        {%- endfor %}
-        {%- endif %}
-    }
-{%- endif %}
\ No newline at end of file
diff --git a/crate_universe/src/rendering/templates/partials/starlark/selectable_list.j2 b/crate_universe/src/rendering/templates/partials/starlark/selectable_list.j2
deleted file mode 100644
index 2641713..0000000
--- a/crate_universe/src/rendering/templates/partials/starlark/selectable_list.j2
+++ /dev/null
@@ -1,31 +0,0 @@
-select_with_or({
-    {%- set selectable = selectable | default(value=default_select_list) %}
-    {%- for cfg, values in selectable.selects %}
-        # {{ cfg }}
-    {%- if cfg in context.conditions and context.conditions[cfg] | length %}
-        (
-            {%- for triple in context.conditions[cfg] %}
-            "{{ platform_label(triple = triple) }}",
-            {%- endfor %}
-        ): [
-            # Target Deps
-            {%- for val in values %}
-            "{{ val }}",
-            {%- endfor %}
-
-            # Common Deps
-            {%- for val in selectable.common %}
-            "{{ val }}",
-            {%- endfor %}
-        ],
-    {%- else %}
-            # No supported platform triples for cfg: '{{ cfg }}'
-            # Skipped dependencies: {{ values | json_encode | safe }}
-    {%- endif %}
-    {%- endfor %}
-        "//conditions:default": [
-            {%- for val in selectable.common %}
-            "{{ val }}",
-            {%- endfor %}
-        ],
-    })
\ No newline at end of file
diff --git a/crate_universe/src/utils/starlark.rs b/crate_universe/src/utils/starlark.rs
index aaba805..474b826 100644
--- a/crate_universe/src/utils/starlark.rs
+++ b/crate_universe/src/utils/starlark.rs
@@ -20,13 +20,26 @@
 #[derive(Serialize)]
 #[serde(untagged)]
 pub enum Starlark {
+    Load(Load),
     Package(Package),
     ExportsFiles(ExportsFiles),
     Filegroup(Filegroup),
     Alias(Alias),
+    CargoBuildScript(CargoBuildScript),
+    #[serde(serialize_with = "serialize::rust_proc_macro")]
+    RustProcMacro(RustProcMacro),
+    #[serde(serialize_with = "serialize::rust_library")]
+    RustLibrary(RustLibrary),
+    #[serde(serialize_with = "serialize::rust_binary")]
+    RustBinary(RustBinary),
 
     #[serde(skip_serializing)]
-    Comment(String),
+    Verbatim(String),
+}
+
+pub struct Load {
+    pub bzl: String,
+    pub items: Set<String>,
 }
 
 pub struct Package {
@@ -53,6 +66,175 @@
     pub tags: Set<String>,
 }
 
+#[derive(Serialize)]
+#[serde(rename = "cargo_build_script")]
+pub struct CargoBuildScript {
+    pub name: String,
+    #[serde(
+        skip_serializing_if = "SelectDict::is_empty",
+        serialize_with = "SelectDict::serialize_starlark"
+    )]
+    pub aliases: SelectDict<WithOriginalConfigurations<String>>,
+    #[serde(
+        skip_serializing_if = "SelectDict::is_empty",
+        serialize_with = "SelectDict::serialize_starlark"
+    )]
+    pub build_script_env: SelectDict<WithOriginalConfigurations<String>>,
+    #[serde(skip_serializing_if = "Data::is_empty")]
+    pub compile_data: Data,
+    #[serde(skip_serializing_if = "Set::is_empty")]
+    pub crate_features: Set<String>,
+    pub crate_name: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub crate_root: Option<String>,
+    #[serde(skip_serializing_if = "Data::is_empty")]
+    pub data: Data,
+    #[serde(
+        skip_serializing_if = "SelectList::is_empty",
+        serialize_with = "SelectList::serialize_starlark"
+    )]
+    pub deps: SelectList<WithOriginalConfigurations<String>>,
+    pub edition: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub linker_script: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub links: Option<String>,
+    #[serde(
+        skip_serializing_if = "SelectList::is_empty",
+        serialize_with = "SelectList::serialize_starlark"
+    )]
+    pub proc_macro_deps: SelectList<WithOriginalConfigurations<String>>,
+    #[serde(
+        skip_serializing_if = "SelectDict::is_empty",
+        serialize_with = "SelectDict::serialize_starlark"
+    )]
+    pub rustc_env: SelectDict<WithOriginalConfigurations<String>>,
+    #[serde(
+        skip_serializing_if = "SelectList::is_empty",
+        serialize_with = "SelectList::serialize_starlark"
+    )]
+    pub rustc_env_files: SelectList<WithOriginalConfigurations<String>>,
+    #[serde(
+        skip_serializing_if = "SelectList::is_empty",
+        serialize_with = "SelectList::serialize_starlark"
+    )]
+    pub rustc_flags: SelectList<WithOriginalConfigurations<String>>,
+    pub srcs: Glob,
+    #[serde(skip_serializing_if = "Set::is_empty")]
+    pub tags: Set<String>,
+    #[serde(
+        skip_serializing_if = "SelectList::is_empty",
+        serialize_with = "SelectList::serialize_starlark"
+    )]
+    pub tools: SelectList<WithOriginalConfigurations<String>>,
+    #[serde(skip_serializing_if = "Set::is_empty")]
+    pub toolchains: Set<String>,
+    pub version: String,
+    pub visibility: Set<String>,
+}
+
+#[derive(Serialize)]
+pub struct RustProcMacro {
+    pub name: String,
+    #[serde(
+        skip_serializing_if = "SelectList::is_empty",
+        serialize_with = "SelectList::serialize_starlark"
+    )]
+    pub deps: SelectList<WithOriginalConfigurations<String>>,
+    #[serde(
+        skip_serializing_if = "SelectList::is_empty",
+        serialize_with = "SelectList::serialize_starlark"
+    )]
+    pub proc_macro_deps: SelectList<WithOriginalConfigurations<String>>,
+    #[serde(
+        skip_serializing_if = "SelectDict::is_empty",
+        serialize_with = "SelectDict::serialize_starlark"
+    )]
+    pub aliases: SelectDict<WithOriginalConfigurations<String>>,
+    #[serde(flatten)]
+    pub common: CommonAttrs,
+}
+
+#[derive(Serialize)]
+pub struct RustLibrary {
+    pub name: String,
+    #[serde(
+        skip_serializing_if = "SelectList::is_empty",
+        serialize_with = "SelectList::serialize_starlark"
+    )]
+    pub deps: SelectList<WithOriginalConfigurations<String>>,
+    #[serde(
+        skip_serializing_if = "SelectList::is_empty",
+        serialize_with = "SelectList::serialize_starlark"
+    )]
+    pub proc_macro_deps: SelectList<WithOriginalConfigurations<String>>,
+    #[serde(
+        skip_serializing_if = "SelectDict::is_empty",
+        serialize_with = "SelectDict::serialize_starlark"
+    )]
+    pub aliases: SelectDict<WithOriginalConfigurations<String>>,
+    #[serde(flatten)]
+    pub common: CommonAttrs,
+}
+
+#[derive(Serialize)]
+pub struct RustBinary {
+    pub name: String,
+    #[serde(
+        skip_serializing_if = "SelectList::is_empty",
+        serialize_with = "SelectList::serialize_starlark"
+    )]
+    pub deps: SelectList<WithOriginalConfigurations<String>>,
+    #[serde(
+        skip_serializing_if = "SelectList::is_empty",
+        serialize_with = "SelectList::serialize_starlark"
+    )]
+    pub proc_macro_deps: SelectList<WithOriginalConfigurations<String>>,
+    #[serde(
+        skip_serializing_if = "SelectDict::is_empty",
+        serialize_with = "SelectDict::serialize_starlark"
+    )]
+    pub aliases: SelectDict<WithOriginalConfigurations<String>>,
+    #[serde(flatten)]
+    pub common: CommonAttrs,
+}
+
+#[derive(Serialize)]
+pub struct CommonAttrs {
+    #[serde(skip_serializing_if = "Data::is_empty")]
+    pub compile_data: Data,
+    #[serde(skip_serializing_if = "Set::is_empty")]
+    pub crate_features: Set<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub crate_root: Option<String>,
+    #[serde(skip_serializing_if = "Data::is_empty")]
+    pub data: Data,
+    pub edition: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub linker_script: Option<String>,
+    #[serde(
+        skip_serializing_if = "SelectDict::is_empty",
+        serialize_with = "SelectDict::serialize_starlark"
+    )]
+    pub rustc_env: SelectDict<WithOriginalConfigurations<String>>,
+    #[serde(
+        skip_serializing_if = "SelectList::is_empty",
+        serialize_with = "SelectList::serialize_starlark"
+    )]
+    pub rustc_env_files: SelectList<WithOriginalConfigurations<String>>,
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub rustc_flags: Vec<String>,
+    pub srcs: Glob,
+    #[serde(skip_serializing_if = "Set::is_empty")]
+    pub tags: Set<String>,
+    pub version: String,
+}
+
+pub struct Data {
+    pub glob: Glob,
+    pub select: SelectList<WithOriginalConfigurations<String>>,
+}
+
 impl Package {
     pub fn default_visibility_public() -> Self {
         let mut default_visibility = Set::new();
@@ -67,7 +249,7 @@
         if !content.is_empty() {
             content.push('\n');
         }
-        if let Starlark::Comment(comment) = call {
+        if let Starlark::Verbatim(comment) = call {
             content.push_str(comment);
         } else {
             content.push_str(&serde_starlark::to_string(call)?);
diff --git a/crate_universe/src/utils/starlark/glob.rs b/crate_universe/src/utils/starlark/glob.rs
index df3d128..338985d 100644
--- a/crate_universe/src/utils/starlark/glob.rs
+++ b/crate_universe/src/utils/starlark/glob.rs
@@ -16,22 +16,21 @@
             exclude: BTreeSet::new(),
         }
     }
+
+    pub fn is_empty(&self) -> bool {
+        self.include.is_empty()
+        // Note: self.exclude intentionally not considered. A glob is empty if
+        // there are no included globs. A glob cannot have only excludes.
+    }
 }
 
 impl Serialize for Glob {
-    #[allow(unknown_lints, renamed_and_removed_lints)]
-    #[allow(clippy::overly_complex_bool_expr)] // clippy 1.65+
-    #[allow(clippy::logic_bug)] // clippy 1.64 and older
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
     {
-        if false && self.exclude.is_empty() {
+        if self.exclude.is_empty() {
             // Serialize as glob([...]).
-            // FIXME(dtolnay): this is disabled for now because the tera
-            // template glob.j2 counts on the serialization to have separate
-            // "include" and "exclude" fields. This can be enabled when the tera
-            // use of globs is replaced with serde_starlark.
             let mut call = serializer.serialize_tuple_struct("glob", 1)?;
             call.serialize_field(&self.include)?;
             call.end()
diff --git a/crate_universe/src/utils/starlark/select.rs b/crate_universe/src/utils/starlark/select.rs
index 42281bb..8e7f41f 100644
--- a/crate_universe/src/utils/starlark/select.rs
+++ b/crate_universe/src/utils/starlark/select.rs
@@ -1,6 +1,11 @@
-use serde::{Deserialize, Serialize};
 use std::collections::{btree_set, BTreeMap, BTreeSet};
-use std::iter::once;
+use std::iter::{once, FromIterator};
+
+use serde::ser::{SerializeMap, SerializeTupleStruct, Serializer};
+use serde::{Deserialize, Serialize};
+use serde_starlark::{FunctionCall, LineComment, MULTILINE};
+
+use crate::utils::starlark::serialize::MultilineArray;
 
 pub trait SelectMap<T, U> {
     // A selectable should also implement a `map` function allowing one type of selectable
@@ -19,8 +24,16 @@
 
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Clone)]
 pub struct SelectList<T: Ord> {
+    // Invariant: any T in `common` is not anywhere in `selects`.
     common: BTreeSet<T>,
+    // Invariant: none of the sets are empty.
     selects: BTreeMap<String, BTreeSet<T>>,
+    // Elements that used to be in `selects` before the most recent
+    // `remap_configurations` operation, but whose old configuration did not get
+    // mapped to any new configuration. They could be ignored, but are preserved
+    // here to generate comments that help the user understand what happened.
+    #[serde(skip_serializing_if = "BTreeSet::is_empty", default = "BTreeSet::new")]
+    unmapped: BTreeSet<T>,
 }
 
 impl<T: Ord> Default for SelectList<T> {
@@ -28,6 +41,7 @@
         Self {
             common: BTreeSet::new(),
             selects: BTreeMap::new(),
+            unmapped: BTreeSet::new(),
         }
     }
 }
@@ -37,21 +51,18 @@
     pub fn insert(&mut self, value: T, configuration: Option<String>) {
         match configuration {
             None => {
+                self.selects.retain(|_, set| {
+                    set.remove(&value);
+                    !set.is_empty()
+                });
                 self.common.insert(value);
             }
             Some(cfg) => {
-                match self.selects.get_mut(&cfg) {
-                    None => {
-                        let mut set = BTreeSet::new();
-                        set.insert(value);
-                        self.selects.insert(cfg, set);
-                    }
-                    Some(set) => {
-                        set.insert(value);
-                    }
-                };
+                if !self.common.contains(&value) {
+                    self.selects.entry(cfg).or_default().insert(value);
+                }
             }
-        };
+        }
     }
 
     // TODO: This should probably be added to the [Select] trait
@@ -63,96 +74,195 @@
     }
 
     /// Determine whether or not the select should be serialized
-    pub fn should_skip_serializing(&self) -> bool {
-        self.common.is_empty() && self.selects.is_empty()
+    pub fn is_empty(&self) -> bool {
+        self.common.is_empty() && self.selects.is_empty() && self.unmapped.is_empty()
     }
 }
 
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Clone)]
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
 pub struct WithOriginalConfigurations<T> {
     value: T,
-    original_configurations: BTreeSet<Option<String>>,
+    original_configurations: Option<BTreeSet<String>>,
 }
 
 impl<T: Ord + Clone> SelectList<T> {
     /// Generates a new SelectList re-keyed by the given configuration mapping.
     /// This mapping maps from configurations in the current SelectList to sets of
     /// configurations in the new SelectList.
-    ///
-    /// This returns the new SelectList as well as a BTreeMap of unmapped select configurations.
-    pub fn remap_configurations<'a, I, S>(
-        &self,
-        mapping: &'a BTreeMap<String, I>,
-    ) -> (
-        SelectList<WithOriginalConfigurations<T>>,
-        BTreeMap<String, BTreeSet<T>>,
-    )
-    where
-        &'a I: IntoIterator<Item = S>,
-        S: AsRef<str>,
-    {
-        // Map new configuraiton -> value -> old configurations.
-        let mut remapped: BTreeMap<String, BTreeMap<T, BTreeSet<Option<String>>>> = BTreeMap::new();
-        let mut unmapped: BTreeMap<String, BTreeSet<T>> = BTreeMap::new();
+    pub fn remap_configurations(
+        self,
+        mapping: &BTreeMap<String, BTreeSet<String>>,
+    ) -> SelectList<WithOriginalConfigurations<T>> {
+        // Map new configuration -> value -> old configurations.
+        let mut remapped: BTreeMap<String, BTreeMap<T, BTreeSet<String>>> = BTreeMap::new();
+        // Map value -> old configurations.
+        let mut unmapped: BTreeMap<T, BTreeSet<String>> = BTreeMap::new();
 
-        for (original_configuration, values) in &self.selects {
-            match mapping.get(original_configuration) {
+        for (original_configuration, values) in self.selects {
+            match mapping.get(&original_configuration) {
                 Some(configurations) => {
                     for configuration in configurations {
-                        for value in values {
+                        for value in &values {
                             remapped
-                                .entry(configuration.as_ref().to_owned())
+                                .entry(configuration.clone())
                                 .or_default()
                                 .entry(value.clone())
                                 .or_default()
-                                .insert(Some(original_configuration.to_owned()));
+                                .insert(original_configuration.clone());
                         }
                     }
                 }
-                None => unmapped
-                    .entry(original_configuration.clone())
-                    .or_default()
-                    .append(&mut values.clone()),
+                None => {
+                    for value in values {
+                        unmapped
+                            .entry(value)
+                            .or_default()
+                            .insert(original_configuration.clone());
+                    }
+                }
             }
         }
-        for value in &self.common {
-            for (_, value_to_configs) in remapped.iter_mut() {
-                value_to_configs
-                    .entry(value.clone())
-                    .or_default()
-                    .insert(None);
-            }
+
+        SelectList {
+            common: self
+                .common
+                .into_iter()
+                .map(|value| WithOriginalConfigurations {
+                    value,
+                    original_configurations: None,
+                })
+                .collect(),
+            selects: remapped
+                .into_iter()
+                .map(|(new_configuration, value_to_original_configuration)| {
+                    (
+                        new_configuration,
+                        value_to_original_configuration
+                            .into_iter()
+                            .map(
+                                |(value, original_configurations)| WithOriginalConfigurations {
+                                    value,
+                                    original_configurations: Some(original_configurations),
+                                },
+                            )
+                            .collect(),
+                    )
+                })
+                .collect(),
+            unmapped: unmapped
+                .into_iter()
+                .map(
+                    |(value, original_configurations)| WithOriginalConfigurations {
+                        value,
+                        original_configurations: Some(original_configurations),
+                    },
+                )
+                .collect(),
         }
-        (
-            SelectList {
-                common: self
-                    .common
-                    .iter()
-                    .map(|value| WithOriginalConfigurations {
-                        value: value.clone(),
-                        original_configurations: BTreeSet::from([None]),
-                    })
-                    .collect(),
-                selects: remapped
-                    .into_iter()
-                    .map(|(new_configuration, value_to_original_configuration)| {
-                        (
-                            new_configuration,
-                            value_to_original_configuration
-                                .into_iter()
-                                .map(|(value, original_configurations)| {
-                                    WithOriginalConfigurations {
-                                        value,
-                                        original_configurations,
-                                    }
-                                })
-                                .collect(),
-                        )
-                    })
-                    .collect(),
-            },
-            unmapped,
-        )
+    }
+}
+
+#[derive(Serialize)]
+#[serde(rename = "selects.NO_MATCHING_PLATFORM_TRIPLES")]
+struct NoMatchingPlatformTriples;
+
+// TODO: after removing the remaining tera template usages of SelectList, this
+// inherent method should become the Serialize impl.
+impl<T: Ord> SelectList<T> {
+    pub fn serialize_starlark<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        T: Serialize,
+        S: Serializer,
+    {
+        // Output looks like:
+        //
+        //     [
+        //         "common...",
+        //     ] + select({
+        //         "configuration": [
+        //             "value...",  # cfg(whatever)
+        //         ],
+        //         "//conditions:default": [],
+        //     })
+        //
+        // The common part and select are each omitted if they are empty (except
+        // if the entire thing is empty, in which case we serialize the common
+        // part to get an empty array).
+        //
+        // If there are unmapped entries, we include them like this:
+        //
+        //     [
+        //         "common...",
+        //     ] + selects.with_unmapped({
+        //         "configuration": [
+        //             "value...",  # cfg(whatever)
+        //         ],
+        //         "//conditions:default": [],
+        //         selects.NO_MATCHING_PLATFORM_TRIPLES: [
+        //             "value...",  # cfg(obscure)
+        //         ],
+        //     })
+
+        let mut plus = serializer.serialize_tuple_struct("+", MULTILINE)?;
+
+        if !self.common.is_empty() || self.selects.is_empty() && self.unmapped.is_empty() {
+            plus.serialize_field(&MultilineArray(&self.common))?;
+        }
+
+        if !self.selects.is_empty() || !self.unmapped.is_empty() {
+            struct SelectInner<'a, T: Ord>(&'a SelectList<T>);
+
+            impl<'a, T> Serialize for SelectInner<'a, T>
+            where
+                T: Ord + Serialize,
+            {
+                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+                where
+                    S: Serializer,
+                {
+                    let mut map = serializer.serialize_map(Some(MULTILINE))?;
+                    for (cfg, value) in &self.0.selects {
+                        map.serialize_entry(cfg, &MultilineArray(value))?;
+                    }
+                    map.serialize_entry("//conditions:default", &[] as &[T])?;
+                    if !self.0.unmapped.is_empty() {
+                        map.serialize_entry(
+                            &NoMatchingPlatformTriples,
+                            &MultilineArray(&self.0.unmapped),
+                        )?;
+                    }
+                    map.end()
+                }
+            }
+
+            let function = if self.unmapped.is_empty() {
+                "select"
+            } else {
+                "selects.with_unmapped"
+            };
+
+            plus.serialize_field(&FunctionCall::new(function, [SelectInner(self)]))?;
+        }
+
+        plus.end()
+    }
+}
+
+impl<T> Serialize for WithOriginalConfigurations<T>
+where
+    T: Serialize,
+{
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        if let Some(original_configurations) = &self.original_configurations {
+            let comment =
+                Vec::from_iter(original_configurations.iter().map(String::as_str)).join(", ");
+            LineComment::new(&self.value, &comment).serialize(serializer)
+        } else {
+            self.value.serialize(serializer)
+        }
     }
 }
 
@@ -170,21 +280,43 @@
     type Mapped = SelectList<U>;
 
     fn map<F: Copy + Fn(T) -> U>(self, func: F) -> Self::Mapped {
+        let common: BTreeSet<U> = self.common.into_iter().map(func).collect();
+        let selects: BTreeMap<String, BTreeSet<U>> = self
+            .selects
+            .into_iter()
+            .filter_map(|(key, set)| {
+                let set: BTreeSet<U> = set
+                    .into_iter()
+                    .map(func)
+                    .filter(|value| !common.contains(value))
+                    .collect();
+                if set.is_empty() {
+                    None
+                } else {
+                    Some((key, set))
+                }
+            })
+            .collect();
         SelectList {
-            common: self.common.into_iter().map(func).collect(),
-            selects: self
-                .selects
-                .into_iter()
-                .map(|(key, map)| (key, map.into_iter().map(func).collect()))
-                .collect(),
+            common,
+            selects,
+            unmapped: self.unmapped.into_iter().map(func).collect(),
         }
     }
 }
 
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Clone)]
 pub struct SelectDict<T: Ord> {
+    // Invariant: keys in this map are not in any of the inner maps of `selects`.
     common: BTreeMap<String, T>,
+    // Invariant: none of the inner maps are empty.
     selects: BTreeMap<String, BTreeMap<String, T>>,
+    // Elements that used to be in `selects` before the most recent
+    // `remap_configurations` operation, but whose old configuration did not get
+    // mapped to any new configuration. They could be ignored, but are preserved
+    // here to generate comments that help the user understand what happened.
+    #[serde(skip_serializing_if = "BTreeMap::is_empty", default = "BTreeMap::new")]
+    unmapped: BTreeMap<String, T>,
 }
 
 impl<T: Ord> Default for SelectDict<T> {
@@ -192,35 +324,200 @@
         Self {
             common: BTreeMap::new(),
             selects: BTreeMap::new(),
+            unmapped: BTreeMap::new(),
         }
     }
 }
 
 impl<T: Ord> SelectDict<T> {
-    // TODO: This should probably be added to the [Select] trait
-    pub fn insert(&mut self, value: BTreeMap<String, T>, configuration: Option<String>) {
+    pub fn insert(&mut self, key: String, value: T, configuration: Option<String>) {
         match configuration {
             None => {
-                self.common.extend(value);
+                self.selects.retain(|_, map| {
+                    map.remove(&key);
+                    !map.is_empty()
+                });
+                self.common.insert(key, value);
             }
             Some(cfg) => {
-                match self.selects.get_mut(&cfg) {
-                    None => {
-                        let mut set = BTreeMap::new();
-                        set.extend(value);
-                        self.selects.insert(cfg, set);
-                    }
-                    Some(set) => {
-                        set.extend(value);
-                    }
-                };
+                if !self.common.contains_key(&key) {
+                    self.selects.entry(cfg).or_default().insert(key, value);
+                }
             }
-        };
+        }
     }
 
-    /// Determine whether or not the select should be serialized
-    pub fn should_skip_serializing(&self) -> bool {
-        self.common.is_empty() && self.selects.is_empty()
+    pub fn extend(&mut self, entries: BTreeMap<String, T>, configuration: Option<String>) {
+        for (key, value) in entries {
+            self.insert(key, value, configuration.clone());
+        }
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.common.is_empty() && self.selects.is_empty() && self.unmapped.is_empty()
+    }
+}
+
+impl<T: Ord + Clone> SelectDict<T> {
+    /// Generates a new SelectDict re-keyed by the given configuration mapping.
+    /// This mapping maps from configurations in the current SelectDict to sets
+    /// of configurations in the new SelectDict.
+    pub fn remap_configurations(
+        self,
+        mapping: &BTreeMap<String, BTreeSet<String>>,
+    ) -> SelectDict<WithOriginalConfigurations<T>> {
+        // Map new configuration -> entry -> old configurations.
+        let mut remapped: BTreeMap<String, BTreeMap<(String, T), BTreeSet<String>>> =
+            BTreeMap::new();
+        // Map entry -> old configurations.
+        let mut unmapped: BTreeMap<(String, T), BTreeSet<String>> = BTreeMap::new();
+
+        for (original_configuration, entries) in self.selects {
+            match mapping.get(&original_configuration) {
+                Some(configurations) => {
+                    for configuration in configurations {
+                        for (key, value) in &entries {
+                            remapped
+                                .entry(configuration.clone())
+                                .or_default()
+                                .entry((key.clone(), value.clone()))
+                                .or_default()
+                                .insert(original_configuration.clone());
+                        }
+                    }
+                }
+                None => {
+                    for (key, value) in entries {
+                        unmapped
+                            .entry((key, value))
+                            .or_default()
+                            .insert(original_configuration.clone());
+                    }
+                }
+            }
+        }
+
+        SelectDict {
+            common: self
+                .common
+                .into_iter()
+                .map(|(key, value)| {
+                    (
+                        key,
+                        WithOriginalConfigurations {
+                            value,
+                            original_configurations: None,
+                        },
+                    )
+                })
+                .collect(),
+            selects: remapped
+                .into_iter()
+                .map(|(new_configuration, entry_to_original_configuration)| {
+                    (
+                        new_configuration,
+                        entry_to_original_configuration
+                            .into_iter()
+                            .map(|((key, value), original_configurations)| {
+                                (
+                                    key,
+                                    WithOriginalConfigurations {
+                                        value,
+                                        original_configurations: Some(original_configurations),
+                                    },
+                                )
+                            })
+                            .collect(),
+                    )
+                })
+                .collect(),
+            unmapped: unmapped
+                .into_iter()
+                .map(|((key, value), original_configurations)| {
+                    (
+                        key,
+                        WithOriginalConfigurations {
+                            value,
+                            original_configurations: Some(original_configurations),
+                        },
+                    )
+                })
+                .collect(),
+        }
+    }
+}
+
+// TODO: after removing the remaining tera template usages of SelectDict, this
+// inherent method should become the Serialize impl.
+impl<T: Ord + Serialize> SelectDict<T> {
+    pub fn serialize_starlark<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        // If there are no platform-specific entries, we output just an ordinary
+        // dict.
+        //
+        // If there are platform-specific ones, we use the following. Ideally it
+        // could be done as `dicts.add({...}, select({...}))` but bazel_skylib's
+        // dicts.add does not support selects.
+        //
+        //     select({
+        //         "configuration": {
+        //             "common-key": "common-value",
+        //             "plat-key": "plat-value",  # cfg(whatever)
+        //         },
+        //         "//conditions:default": {},
+        //     })
+        //
+        // If there are unmapped entries, we include them like this:
+        //
+        //     selects.with_unmapped({
+        //         "configuration": {
+        //             "common-key": "common-value",
+        //             "plat-key": "plat-value",  # cfg(whatever)
+        //         },
+        //         "//conditions:default": [],
+        //         selects.NO_MATCHING_PLATFORM_TRIPLES: {
+        //             "unmapped-key": "unmapped-value",  # cfg(obscure)
+        //         },
+        //     })
+
+        if self.selects.is_empty() && self.unmapped.is_empty() {
+            return self.common.serialize(serializer);
+        }
+
+        struct SelectInner<'a, T: Ord>(&'a SelectDict<T>);
+
+        impl<'a, T> Serialize for SelectInner<'a, T>
+        where
+            T: Ord + Serialize,
+        {
+            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+            where
+                S: Serializer,
+            {
+                let mut map = serializer.serialize_map(Some(MULTILINE))?;
+                for (cfg, value) in &self.0.selects {
+                    let mut combined = BTreeMap::new();
+                    combined.extend(&self.0.common);
+                    combined.extend(value);
+                    map.serialize_entry(cfg, &combined)?;
+                }
+                map.serialize_entry("//conditions:default", &self.0.common)?;
+                if !self.0.unmapped.is_empty() {
+                    map.serialize_entry(&NoMatchingPlatformTriples, &self.0.unmapped)?;
+                }
+                map.end()
+            }
+        }
+
+        let function = if self.unmapped.is_empty() {
+            "select"
+        } else {
+            "selects.with_unmapped"
+        };
+
+        FunctionCall::new(function, [SelectInner(self)]).serialize(serializer)
     }
 }
 
@@ -234,29 +531,12 @@
     }
 }
 
-impl<T: Ord, U: Ord> SelectMap<T, U> for SelectDict<T> {
-    type Mapped = SelectDict<U>;
-
-    fn map<F: Copy + Fn(T) -> U>(self, func: F) -> Self::Mapped {
-        SelectDict {
-            common: self
-                .common
-                .into_iter()
-                .map(|(key, val)| (key, func(val)))
-                .collect(),
-            selects: self
-                .selects
-                .into_iter()
-                .map(|(key, map)| (key, map.into_iter().map(|(k, v)| (k, func(v))).collect()))
-                .collect(),
-        }
-    }
-}
-
 #[cfg(test)]
 mod test {
     use super::*;
 
+    use indoc::indoc;
+
     #[test]
     fn remap_select_list_configurations() {
         let mut select_list = SelectList::default();
@@ -283,95 +563,100 @@
         expected.insert(
             WithOriginalConfigurations {
                 value: "dep-a".to_owned(),
-                original_configurations: BTreeSet::from([
-                    Some("cfg(macos)".to_owned()),
-                    Some("cfg(x86_64)".to_owned()),
-                ]),
+                original_configurations: Some(BTreeSet::from([
+                    "cfg(macos)".to_owned(),
+                    "cfg(x86_64)".to_owned(),
+                ])),
             },
             Some("x86_64-macos".to_owned()),
         );
         expected.insert(
             WithOriginalConfigurations {
                 value: "dep-b".to_owned(),
-                original_configurations: BTreeSet::from([Some("cfg(macos)".to_owned())]),
+                original_configurations: Some(BTreeSet::from(["cfg(macos)".to_owned()])),
             },
             Some("x86_64-macos".to_owned()),
         );
         expected.insert(
             WithOriginalConfigurations {
                 value: "dep-c".to_owned(),
-                original_configurations: BTreeSet::from([Some("cfg(x86_64)".to_owned())]),
+                original_configurations: Some(BTreeSet::from(["cfg(x86_64)".to_owned()])),
             },
             Some("x86_64-macos".to_owned()),
         );
         expected.insert(
             WithOriginalConfigurations {
-                value: "dep-d".to_owned(),
-                original_configurations: BTreeSet::from([None, Some("cfg(macos)".to_owned())]),
-            },
-            Some("x86_64-macos".to_owned()),
-        );
-
-        expected.insert(
-            WithOriginalConfigurations {
                 value: "dep-a".to_owned(),
-                original_configurations: BTreeSet::from([Some("cfg(macos)".to_owned())]),
+                original_configurations: Some(BTreeSet::from(["cfg(macos)".to_owned()])),
             },
             Some("aarch64-macos".to_owned()),
         );
         expected.insert(
             WithOriginalConfigurations {
                 value: "dep-b".to_owned(),
-                original_configurations: BTreeSet::from([Some("cfg(macos)".to_owned())]),
+                original_configurations: Some(BTreeSet::from(["cfg(macos)".to_owned()])),
             },
             Some("aarch64-macos".to_owned()),
         );
         expected.insert(
             WithOriginalConfigurations {
-                value: "dep-d".to_owned(),
-                original_configurations: BTreeSet::from([None, Some("cfg(macos)".to_owned())]),
-            },
-            Some("aarch64-macos".to_owned()),
-        );
-
-        expected.insert(
-            WithOriginalConfigurations {
                 value: "dep-a".to_owned(),
-                original_configurations: BTreeSet::from([Some("cfg(x86_64)".to_owned())]),
+                original_configurations: Some(BTreeSet::from(["cfg(x86_64)".to_owned()])),
             },
             Some("x86_64-linux".to_owned()),
         );
         expected.insert(
             WithOriginalConfigurations {
                 value: "dep-c".to_owned(),
-                original_configurations: BTreeSet::from([Some("cfg(x86_64)".to_owned())]),
+                original_configurations: Some(BTreeSet::from(["cfg(x86_64)".to_owned()])),
             },
             Some("x86_64-linux".to_owned()),
         );
         expected.insert(
             WithOriginalConfigurations {
                 value: "dep-d".to_owned(),
-                original_configurations: BTreeSet::from([None]),
-            },
-            Some("x86_64-linux".to_owned()),
-        );
-
-        expected.insert(
-            WithOriginalConfigurations {
-                value: "dep-d".to_owned(),
-                original_configurations: BTreeSet::from([None]),
+                original_configurations: None,
             },
             None,
         );
 
-        let expected_unmapped = BTreeMap::from([(
-            "cfg(pdp11)".to_owned(),
-            BTreeSet::from(["dep-e".to_owned()]),
-        )]);
+        expected.unmapped.insert(WithOriginalConfigurations {
+            value: "dep-e".to_owned(),
+            original_configurations: Some(BTreeSet::from(["cfg(pdp11)".to_owned()])),
+        });
+
+        let select_list = select_list.remap_configurations(&mapping);
+        assert_eq!(select_list, expected);
+
+        let expected_starlark = indoc! {r#"
+            [
+                "dep-d",
+            ] + selects.with_unmapped({
+                "aarch64-macos": [
+                    "dep-a",  # cfg(macos)
+                    "dep-b",  # cfg(macos)
+                ],
+                "x86_64-linux": [
+                    "dep-a",  # cfg(x86_64)
+                    "dep-c",  # cfg(x86_64)
+                ],
+                "x86_64-macos": [
+                    "dep-a",  # cfg(macos), cfg(x86_64)
+                    "dep-b",  # cfg(macos)
+                    "dep-c",  # cfg(x86_64)
+                ],
+                "//conditions:default": [],
+                selects.NO_MATCHING_PLATFORM_TRIPLES: [
+                    "dep-e",  # cfg(pdp11)
+                ],
+            })
+        "#};
 
         assert_eq!(
-            &select_list.remap_configurations(&mapping),
-            &(expected, expected_unmapped)
+            select_list
+                .serialize_starlark(serde_starlark::Serializer)
+                .unwrap(),
+            expected_starlark,
         );
     }
 }
diff --git a/crate_universe/src/utils/starlark/serialize.rs b/crate_universe/src/utils/starlark/serialize.rs
index e91cd82..4d362c7 100644
--- a/crate_universe/src/utils/starlark/serialize.rs
+++ b/crate_universe/src/utils/starlark/serialize.rs
@@ -1,7 +1,85 @@
-use super::{ExportsFiles, Package};
-use serde::ser::{Serialize, SerializeStruct, SerializeTupleStruct, Serializer};
+use serde::ser::{SerializeSeq, SerializeStruct, SerializeTupleStruct, Serializer};
+use serde::Serialize;
 use serde_starlark::{FunctionCall, MULTILINE, ONELINE};
 
+use super::{
+    Data, ExportsFiles, Load, Package, RustBinary, RustLibrary, RustProcMacro, SelectList,
+};
+
+// For structs that contain #[serde(flatten)], a quirk of how Serde processes
+// that attribute is that they get serialized as a map, not struct. In Starlark
+// unlike in JSON, maps and structs are differently serialized, so we need to
+// help fill in the function name or else we'd get a Starlark map instead.
+pub fn rust_proc_macro<S>(rule: &RustProcMacro, serializer: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    FunctionCall::new("rust_proc_macro", rule).serialize(serializer)
+}
+
+pub fn rust_library<S>(rule: &RustLibrary, serializer: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    FunctionCall::new("rust_library", rule).serialize(serializer)
+}
+
+pub fn rust_binary<S>(rule: &RustBinary, serializer: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    FunctionCall::new("rust_binary", rule).serialize(serializer)
+}
+
+// Serialize an array with each element on its own line, even if there is just a
+// single element which serde_starlark would ordinarily place on the same line
+// as the array brackets.
+pub struct MultilineArray<'a, A>(pub &'a A);
+
+impl<'a, A, T> Serialize for MultilineArray<'a, A>
+where
+    &'a A: IntoIterator<Item = &'a T>,
+    T: Serialize + 'a,
+{
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut array = serializer.serialize_seq(Some(serde_starlark::MULTILINE))?;
+        for element in self.0 {
+            array.serialize_element(element)?;
+        }
+        array.end()
+    }
+}
+
+// TODO: This can go away after SelectList's derived Serialize impl (used by
+// tera) goes away and `serialize_starlark` becomes its real Serialize impl.
+#[derive(Serialize)]
+#[serde(transparent)]
+pub struct SelectListWrapper<'a, T: Ord + Serialize>(
+    #[serde(serialize_with = "SelectList::serialize_starlark")] &'a SelectList<T>,
+);
+
+impl Serialize for Load {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let line = if self.items.len() > 1 {
+            MULTILINE
+        } else {
+            ONELINE
+        };
+        let mut call = serializer.serialize_tuple_struct("load", line)?;
+        call.serialize_field(&self.bzl)?;
+        for item in &self.items {
+            call.serialize_field(item)?;
+        }
+        call.end()
+    }
+}
+
 impl Serialize for Package {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
@@ -23,3 +101,25 @@
         call.end()
     }
 }
+
+impl Data {
+    pub fn is_empty(&self) -> bool {
+        self.glob.is_empty() && self.select.is_empty()
+    }
+}
+
+impl Serialize for Data {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut plus = serializer.serialize_tuple_struct("+", MULTILINE)?;
+        if !self.glob.is_empty() {
+            plus.serialize_field(&self.glob)?;
+        }
+        if !self.select.is_empty() || self.glob.is_empty() {
+            plus.serialize_field(&SelectListWrapper(&self.select))?;
+        }
+        plus.end()
+    }
+}