blob: b9d6ebab1ff87d53344bb3fbdae0861109c3ae68 [file]
use cargo_raze::context::{CrateContext, CrateDependencyContext, CrateTargetedDepContext};
use semver::Version;
use serde::Serialize;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::io::Write;
use tera::{self, Context, Tera};
use crate::config;
use crate::resolver::Dependencies;
use std::path::Path;
pub struct RenderConfig {
pub repo_rule_name: String,
pub repository_template: String,
pub rules_rust_workspace_name: String,
}
pub struct Renderer {
config: RenderConfig,
hash: String,
internal_renderer: Tera,
transitive_renderable_packages: Vec<RenderablePackage>,
member_packages_version_mapping: Dependencies,
label_to_crates: BTreeMap<String, BTreeSet<String>>,
}
// Get default and targeted metadata, collated per Bazel condition (which corresponds to a triple).
// The default metadata is included in every triple.
fn get_per_triple_metadata(package: &CrateContext) -> BTreeMap<String, CrateTargetedDepContext> {
let mut per_triple_metadata: BTreeMap<String, CrateTargetedDepContext> = BTreeMap::new();
// Always add a catch-all to cover the non-targeted dep case.
// We merge in the default_deps after the next loop.
per_triple_metadata.insert(
String::from("//conditions:default"),
CrateTargetedDepContext {
target: "Default".to_owned(),
deps: empty_deps_context(),
conditions: vec!["//conditions:default".to_owned()],
},
);
for dep_context in &package.targeted_deps {
dep_context.conditions.iter().for_each(|condition| {
let targeted_dep_ctx = per_triple_metadata.entry(condition.to_owned()).or_insert(
CrateTargetedDepContext {
target: "".to_owned(),
deps: empty_deps_context(),
conditions: vec![condition.clone()],
},
);
// Mention all the targets that translated into the current condition (ie. current triplet).
targeted_dep_ctx
.target
.push_str(&format!(" {}", &dep_context.target));
targeted_dep_ctx
.deps
.dependencies
.extend(dep_context.deps.dependencies.iter().cloned());
targeted_dep_ctx
.deps
.proc_macro_dependencies
.extend(dep_context.deps.proc_macro_dependencies.iter().cloned());
targeted_dep_ctx
.deps
.data_dependencies
.extend(dep_context.deps.data_dependencies.iter().cloned());
targeted_dep_ctx
.deps
.build_dependencies
.extend(dep_context.deps.build_dependencies.iter().cloned());
targeted_dep_ctx.deps.build_proc_macro_dependencies.extend(
dep_context
.deps
.build_proc_macro_dependencies
.iter()
.cloned(),
);
targeted_dep_ctx
.deps
.build_data_dependencies
.extend(dep_context.deps.build_data_dependencies.iter().cloned());
targeted_dep_ctx
.deps
.dev_dependencies
.extend(dep_context.deps.dev_dependencies.iter().cloned());
targeted_dep_ctx
.deps
.aliased_dependencies
.extend(dep_context.deps.aliased_dependencies.iter().cloned());
});
}
// Now also add the non-targeted deps to each target.
for ctx in per_triple_metadata.values_mut() {
ctx.deps
.dependencies
.extend(package.default_deps.dependencies.iter().cloned());
ctx.deps
.proc_macro_dependencies
.extend(package.default_deps.proc_macro_dependencies.iter().cloned());
ctx.deps
.data_dependencies
.extend(package.default_deps.data_dependencies.iter().cloned());
ctx.deps
.build_dependencies
.extend(package.default_deps.build_dependencies.iter().cloned());
ctx.deps.build_proc_macro_dependencies.extend(
package
.default_deps
.build_proc_macro_dependencies
.iter()
.cloned(),
);
ctx.deps
.build_data_dependencies
.extend(package.default_deps.build_data_dependencies.iter().cloned());
ctx.deps
.dev_dependencies
.extend(package.default_deps.dev_dependencies.iter().cloned());
ctx.deps
.aliased_dependencies
.extend(package.default_deps.aliased_dependencies.iter().cloned());
}
per_triple_metadata
}
fn empty_deps_context() -> CrateDependencyContext {
CrateDependencyContext {
dependencies: vec![],
proc_macro_dependencies: vec![],
data_dependencies: vec![],
build_dependencies: vec![],
build_proc_macro_dependencies: vec![],
build_data_dependencies: vec![],
dev_dependencies: vec![],
aliased_dependencies: vec![],
}
}
impl Renderer {
pub fn new(
config: RenderConfig,
hash: String,
transitive_packages: Vec<CrateContext>,
member_packages_version_mapping: Dependencies,
label_to_crates: BTreeMap<String, BTreeSet<String>>,
) -> Renderer {
let mut internal_renderer = Tera::new("src/not/a/dir/*").unwrap();
internal_renderer.register_function(
"crate_to_repo_rule_name",
|args: &HashMap<String, tera::Value>| {
let value = config::crate_to_repo_rule_name(
string_arg(args, "repo_rule_name")?,
string_arg(args, "package_name")?,
string_arg(args, "package_version")?,
);
Ok(tera::Value::String(value))
},
);
internal_renderer.register_function(
"crate_to_label",
|args: &HashMap<String, tera::Value>| {
let value = config::crate_to_label(
string_arg(args, "repo_rule_name")?,
string_arg(args, "package_name")?,
string_arg(args, "package_version")?,
);
Ok(tera::Value::String(value))
},
);
internal_renderer
.add_raw_templates(vec![
(
"templates/lockfile.template",
include_str!("templates/lockfile.template"),
),
(
"templates/helper_functions.template",
include_str!("templates/helper_functions.template"),
),
(
"templates/partials/build_script.template",
include_str!("templates/partials/build_script.template"),
),
(
"templates/partials/rust_binary.template",
include_str!("templates/partials/rust_binary.template"),
),
(
"templates/partials/rust_library.template",
include_str!("templates/partials/rust_library.template"),
),
(
"templates/partials/common_attrs.template",
include_str!("templates/partials/common_attrs.template"),
),
(
"templates/crate.BUILD.template",
include_str!("templates/crate.BUILD.template"),
),
(
"templates/partials/targeted_aliases.template",
include_str!("templates/partials/targeted_aliases.template"),
),
(
"templates/partials/targeted_dependencies.template",
include_str!("templates/partials/targeted_dependencies.template"),
),
(
"templates/partials/targeted_data_dependencies.template",
include_str!("templates/partials/targeted_data_dependencies.template"),
),
(
"templates/partials/targeted_build_script_dependencies.template",
include_str!("templates/partials/targeted_build_script_dependencies.template"),
),
(
"templates/partials/targeted_build_script_data_dependencies.template",
include_str!(
"templates/partials/targeted_build_script_data_dependencies.template"
),
),
(
"templates/partials/default_data_dependencies.template",
include_str!("templates/partials/default_data_dependencies.template"),
),
(
"templates/partials/git_repository.template",
include_str!("templates/partials/git_repository.template"),
),
(
"templates/partials/http_archive.template",
include_str!("templates/partials/http_archive.template"),
),
])
.unwrap();
let transitive_renderable_packages = transitive_packages
.into_iter()
.map(|mut crate_context| {
let per_triple_metadata = get_per_triple_metadata(&crate_context);
if let Some(git_repo) = &crate_context.source_details.git_data {
if let Some(prefix_to_strip) = &git_repo.path_to_crate_root {
for mut target in crate_context
.targets
.iter_mut()
.chain(crate_context.build_script_target.iter_mut())
{
let path = Path::new(&target.path);
let prefix_to_strip_path = Path::new(prefix_to_strip);
target.path = path
.strip_prefix(prefix_to_strip_path)
.unwrap()
.to_str()
.unwrap()
.to_owned();
}
}
}
let is_proc_macro = crate_context
.targets
.iter()
.any(|target| target.kind == "proc-macro");
RenderablePackage {
crate_context,
per_triple_metadata,
is_proc_macro,
}
})
.collect();
Self {
config,
hash,
internal_renderer,
transitive_renderable_packages,
member_packages_version_mapping,
label_to_crates,
}
}
pub fn render<Out: Write>(&self, output: &mut Out) -> anyhow::Result<()> {
self.render_workspaces(output)?;
writeln!(output, "")?;
self.render_helper_functions(output)
}
// Visible for testing
pub fn render_workspaces<Out: Write>(&self, output: &mut Out) -> anyhow::Result<()> {
let mut context = Context::new();
context.insert("lockfile_hash", &self.hash);
context.insert("crates", &self.transitive_renderable_packages);
context.insert("repo_rule_name", &self.config.repo_rule_name);
context.insert("repository_http_template", &self.config.repository_template);
let rendered_repository_rules = self
.internal_renderer
.render("templates/lockfile.template", &context)?;
write!(output, "{}", &rendered_repository_rules)?;
Ok(())
}
// Visible for testing
pub fn render_helper_functions<Out: Write>(&self, output: &mut Out) -> anyhow::Result<()> {
let mut crate_repo_names_inner = BTreeMap::new();
crate_repo_names_inner.extend(&self.member_packages_version_mapping.normal);
crate_repo_names_inner.extend(&self.member_packages_version_mapping.build);
crate_repo_names_inner.extend(&self.member_packages_version_mapping.dev);
let renderable_packages: Vec<_> = self
.transitive_renderable_packages
.iter()
.filter(|krate| {
crate_repo_names_inner.get(&krate.crate_context.pkg_name)
== Some(&&krate.crate_context.pkg_version)
})
.collect();
let (proc_macro_crates, default_crates): (Vec<_>, Vec<_>) = self
.member_packages_version_mapping
.normal
.iter()
.partition(|(name, version)| {
self.transitive_renderable_packages.iter().any(|package| {
*package.crate_context.pkg_name == **name
&& package.crate_context.pkg_version == **version
&& package.is_proc_macro
})
});
let mut kind_to_labels_to_crate_names = BTreeMap::new();
kind_to_labels_to_crate_names
.insert(Kind::Normal, self.label_to_crate_names(&default_crates));
kind_to_labels_to_crate_names.insert(
Kind::Dev,
self.label_to_crate_names(&self.member_packages_version_mapping.dev.iter().collect()),
);
kind_to_labels_to_crate_names.insert(
Kind::Build,
self.label_to_crate_names(&self.member_packages_version_mapping.build.iter().collect()),
);
kind_to_labels_to_crate_names.insert(
Kind::ProcMacro,
self.label_to_crate_names(&proc_macro_crates),
);
let mut context = Context::new();
context.insert("crates", &renderable_packages);
context.insert("repo_rule_name", &self.config.repo_rule_name);
context.insert(
"kind_to_labels_to_crate_names",
&kind_to_labels_to_crate_names,
);
let rendered_helper_functions = self
.internal_renderer
.render("templates/helper_functions.template", &context)?;
write!(output, "{}", &rendered_helper_functions)?;
Ok(())
}
fn label_to_crate_names(
&self,
crates: &Vec<(&String, &Version)>,
) -> BTreeMap<String, Vec<String>> {
let crate_names: HashSet<&String> = crates.iter().map(|(name, _)| *name).collect();
self.label_to_crates
.iter()
.map(|(label, all_crates)| {
let value = all_crates
.iter()
.filter(|crate_name| crate_names.contains(crate_name))
.map(|crate_name| crate_name.to_owned())
.collect::<Vec<_>>();
(label.clone(), value)
})
.collect()
}
}
fn string_arg<'a, 'b>(
args: &'a HashMap<String, tera::Value>,
name: &'b str,
) -> Result<&'a str, tera::Error> {
let value = args
.get(name)
.ok_or_else(|| tera::Error::msg(&format!("Missing argument {:?}", name)))?;
value.as_str().ok_or_else(|| {
tera::Error::msg(&format!(
"Wrong argument type, expected string for {:?}",
name
))
})
}
#[derive(Serialize)]
struct RenderablePackage {
crate_context: CrateContext,
per_triple_metadata: BTreeMap<String, CrateTargetedDepContext>,
is_proc_macro: bool,
}
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)]
enum Kind {
Normal,
Dev,
Build,
ProcMacro,
}