use crate::condition::Condition;
use crate::config::BuildConfig;
use crate::crates::CrateFiles;
use crate::crates::{Epoch, NormalizedName, VendoredCrate, Visibility};
use crate::deps::{self, DepOfDep};
use crate::group::Group;
use crate::paths;
use std::collections::{HashMap, HashSet};
use std::ops::Deref;
use anyhow::{bail, Context, Result};
use itertools::Itertools;
use serde::Serialize;
#[derive(Default, Serialize)]
pub struct BuildFile {
pub rules: Vec<Rule>,
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct PackageId {
pub name: String,
pub epoch: Option<String>,
}
#[derive(Debug, Default, Serialize)]
pub struct GnVisibility {
pub testonly: bool,
pub public: bool,
}
#[derive(Debug, Serialize)]
pub struct Rule {
pub name: String,
pub gn_visibility: GnVisibility,
pub detail: RuleDetail,
}
#[derive(Clone, Debug, Default, Serialize)]
pub struct RuleDetail {
pub crate_name: Option<String>,
pub epoch: Option<Epoch>,
pub crate_type: String,
pub crate_root: String,
pub sources: Vec<String>,
pub inputs: Vec<String>,
pub edition: String,
pub cargo_pkg_version: String,
pub cargo_pkg_authors: Option<String>,
pub cargo_pkg_name: String,
pub cargo_pkg_description: Option<String>,
pub cargo_pkg_repository: Option<String>,
pub deps: Vec<DepGroup>,
pub build_deps: Vec<DepGroup>,
pub aliased_deps: Vec<(String, PackageId)>,
pub features: Vec<String>,
pub build_root: Option<String>,
pub build_script_sources: Vec<String>,
pub build_script_inputs: Vec<String>,
pub build_script_outputs: Vec<String>,
pub native_libs: Vec<String>,
pub extra_kv: HashMap<String, serde_json::Value>,
pub dep_on_lib: bool,
pub cond: Option<String>,
}
#[derive(Clone, Debug, Serialize)]
pub struct DepGroup {
cond: Option<String>,
packages: Vec<PackageId>,
}
#[derive(Clone, Debug, Default)]
pub struct PerCrateMetadata {
pub build_script_outputs: Vec<String>,
pub gn_variables: Option<String>,
pub visibility: Visibility,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum NameLibStyle {
PackageName,
LibLiteral,
}
pub fn build_file_from_deps<'a, 'b, Iter, GetFiles>(
deps: Iter,
paths: &'b paths::ChromiumPaths,
extra_config: &'b BuildConfig,
name_lib_style: NameLibStyle,
get_files: GetFiles,
) -> Result<BuildFile>
where
Iter: IntoIterator<Item = &'a deps::Package>,
GetFiles: Fn(&VendoredCrate) -> &'b CrateFiles,
{
let mut b = BuildFile { rules: Vec::new() };
for dep in deps {
let crate_id = dep.crate_id();
b.rules.extend(build_rule_from_dep(
dep,
paths,
get_files(&crate_id),
extra_config,
name_lib_style,
)?)
}
Ok(b)
}
pub fn build_rule_from_dep(
dep: &deps::Package,
paths: &paths::ChromiumPaths,
details: &CrateFiles,
extra_config: &BuildConfig,
name_lib_style: NameLibStyle,
) -> Result<Vec<Rule>> {
let cargo_pkg_authors =
if dep.authors.is_empty() { None } else { Some(dep.authors.join(", ")) };
let per_crate_config = extra_config.per_crate_config.get(&*dep.package_name);
let normalized_crate_name = NormalizedName::from_crate_name(&dep.package_name);
let crate_epoch = Epoch::from_version(&dep.version);
let exclude_deps: Vec<String> = per_crate_config
.iter()
.flat_map(|c| &c.exclude_deps_in_gn)
.chain(&extra_config.all_config.exclude_deps_in_gn)
.cloned()
.collect();
let mut extra_kv = extra_config.all_config.extra_kv.clone();
if let Some(per_crate) = per_crate_config {
extra_kv.extend(per_crate.extra_kv.iter().map(|(k, v)| (k.clone(), v.clone())));
}
let allow_first_party_usage = match extra_kv.get("allow_first_party_usage") {
Some(serde_json::Value::Bool(b)) => *b,
_ => dep.is_toplevel_dep,
};
let cond = dep
.dependency_kinds
.values()
.map(|per_kind_info| per_kind_info.condition.clone())
.reduce(Condition::or)
.expect("Each package should have at least one item in `dependency_kinds`")
.to_handlebars_value()?;
let mut detail_template = RuleDetail {
edition: dep.edition.clone(),
cargo_pkg_version: dep.version.to_string(),
cargo_pkg_authors,
cargo_pkg_name: dep.package_name.to_string(),
cargo_pkg_description: dep.description.as_ref().map(|s| s.trim_end().to_string()),
cargo_pkg_repository: dep.repository.as_ref().map(|s| s.trim_end().to_string()),
cond,
extra_kv,
..Default::default()
};
let create_package_id = |dep: &DepOfDep| {
let name = NormalizedName::from_crate_name(&dep.package_name).to_string();
let epoch = match name_lib_style {
NameLibStyle::LibLiteral => Some(Epoch::from_version(&dep.version).to_string()),
NameLibStyle::PackageName => None,
};
PackageId { name, epoch }
};
let normal_deps: Vec<&DepOfDep> = dep
.dependencies
.iter()
.filter(|d| !exclude_deps.iter().any(|e| e.as_str() == &*d.package_name))
.collect();
let build_deps: Vec<&DepOfDep> = dep
.build_dependencies
.iter()
.filter(|d| !exclude_deps.iter().any(|e| e.as_str() == &*d.package_name))
.collect();
let aliased_normal_deps = {
let mut aliases = Vec::new();
for dep in &normal_deps {
let target_name = NormalizedName::from_crate_name(&dep.package_name).to_string();
if target_name != dep.use_name {
aliases.push((dep.use_name.clone(), create_package_id(dep)));
}
}
aliases.sort_unstable();
aliases.dedup();
aliases
};
detail_template.deps = group_deps(&normal_deps, create_package_id)
.with_context(|| format!("Error processing dependencies of {}", dep.package_name))?;
detail_template.build_deps = group_deps(&build_deps, create_package_id)
.with_context(|| format!("Error processing build dependencies of {}", dep.package_name))?;
detail_template.aliased_deps = aliased_normal_deps;
detail_template.sources =
details.sources.iter().map(|p| format!("//{}", paths.to_gn_abs_path(p).unwrap())).collect();
detail_template.inputs =
details.inputs.iter().map(|p| format!("//{}", paths.to_gn_abs_path(p).unwrap())).collect();
detail_template.native_libs = details
.native_libs
.iter()
.map(|p| format!("//{}", paths.to_gn_abs_path(p).unwrap()))
.collect();
let requested_features_for_normal = {
let mut features = dep
.dependency_kinds
.get(&deps::DependencyKind::Normal)
.map(|per_kind_info| per_kind_info.features.clone())
.unwrap_or_default();
features.sort_unstable();
features.dedup();
features
};
let requested_features_for_build = {
let mut features = dep
.dependency_kinds
.get(&deps::DependencyKind::Build)
.map(|per_kind_info| per_kind_info.features.clone())
.unwrap_or_default();
features.sort_unstable();
features.dedup();
features
};
let unexpected_features: Vec<&str> = {
let banned_features =
extra_config.get_combined_set(&dep.package_name, |cfg| &cfg.ban_features);
let mut actual_features = HashSet::new();
actual_features.extend(requested_features_for_normal.iter().map(Deref::deref));
actual_features.extend(requested_features_for_build.iter().map(Deref::deref));
banned_features.intersection(&actual_features).copied().sorted_unstable().collect()
};
if !unexpected_features.is_empty() {
bail!(
"The following crate features are enabled in crate `{}` \
despite being listed in `ban_features`: {}",
&*dep.package_name,
unexpected_features.iter().map(|f| format!("`{f}`")).join(", "),
);
}
if !per_crate_config.map(|config| config.remove_build_rs).unwrap_or(false) {
let build_script_from_src =
dep.build_script.as_ref().map(|p| paths.to_gn_abs_path(p).unwrap());
detail_template.build_root = build_script_from_src.as_ref().map(|p| format!("//{p}"));
detail_template.build_script_sources = build_script_from_src
.as_ref()
.map(|p| format!("//{p}"))
.into_iter()
.chain(
details
.build_script_sources
.iter()
.map(|p| format!("//{}", paths.to_gn_abs_path(p).unwrap())),
)
.collect();
detail_template.build_script_inputs = details
.build_script_inputs
.iter()
.map(|p| format!("//{}", paths.to_gn_abs_path(p).unwrap()))
.collect();
detail_template.build_script_outputs =
if let Some(outs) = per_crate_config.map(|config| &config.build_script_outputs) {
outs.iter().map(|path| path.display().to_string()).collect()
} else {
vec![]
};
}
let mut rules: Vec<Rule> = Vec::new();
for bin_target in &dep.bin_targets {
let bin_root_from_src = paths.to_gn_abs_path(&bin_target.root).unwrap();
let mut bin_detail = detail_template.clone();
bin_detail.crate_type = "bin".to_string();
bin_detail.crate_root = format!("//{bin_root_from_src}");
bin_detail.features = requested_features_for_normal.clone();
if dep.lib_target.is_some() {
bin_detail.dep_on_lib = true;
if bin_detail.deps.is_empty() {
bin_detail.deps.push(DepGroup { cond: None, packages: Vec::new() });
}
}
rules.push(Rule {
name: NormalizedName::from_crate_name(&bin_target.name).to_string(),
gn_visibility: GnVisibility { testonly: dep.group == Group::Test, public: true },
detail: bin_detail,
});
}
if let Some(lib_target) = &dep.lib_target {
use deps::DependencyKind::*;
let lib_root_from_src = paths.to_gn_abs_path(&lib_target.root).unwrap();
for dep_kind in [Normal, Build] {
if !dep.dependency_kinds.contains_key(&dep_kind) {
continue;
}
let lib_rule_name: String = match &dep_kind {
deps::DependencyKind::Normal => match name_lib_style {
NameLibStyle::PackageName => normalized_crate_name.to_string(),
NameLibStyle::LibLiteral => "lib".to_string(),
},
deps::DependencyKind::Build => "buildrs_support".to_string(),
};
let (crate_name, epoch) = match name_lib_style {
NameLibStyle::PackageName => (None, None),
NameLibStyle::LibLiteral => {
(Some(normalized_crate_name.to_string()), Some(crate_epoch))
}
};
let crate_type = lib_target.lib_type.to_string();
let mut lib_detail = detail_template.clone();
lib_detail.crate_name = crate_name;
lib_detail.epoch = epoch;
lib_detail.crate_type = crate_type;
lib_detail.crate_root = format!("//{lib_root_from_src}");
lib_detail.features = match &dep_kind {
Normal => requested_features_for_normal.clone(),
Build => requested_features_for_build.clone(),
};
rules.push(Rule {
name: lib_rule_name.clone(),
gn_visibility: GnVisibility {
testonly: dep.group == Group::Test,
public: allow_first_party_usage,
},
detail: lib_detail,
});
}
}
Ok(rules)
}
fn group_deps(
deps: &[&DepOfDep],
target_name: impl Fn(&DepOfDep) -> PackageId,
) -> Result<Vec<DepGroup>> {
let mut groups = HashMap::<Option<String>, Vec<_>>::new();
for dep in deps {
let cond = dep.condition.to_handlebars_value().with_context(|| {
format!(
"Error processing condition of the following dependency: `{}`",
dep.package_name
)
})?;
groups.entry(cond).or_default().push(target_name(dep));
}
if !groups.is_empty() {
groups.entry(None).or_default();
}
let mut groups: Vec<DepGroup> =
groups.into_iter().map(|(cond, packages)| DepGroup { cond, packages }).collect();
for group in groups.iter_mut() {
group.packages.sort_unstable();
}
groups.sort_unstable_by(|l, r| l.cond.cmp(&r.cond));
Ok(groups)
}