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()
+ }
+}