pub(crate) mod crate_context;
mod platforms;
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use crate::config::{CrateId, RenderConfig};
use crate::context::platforms::resolve_cfg_platforms;
use crate::lockfile::Digest;
use crate::metadata::{Annotations, Dependency};
use crate::select::Select;
use crate::utils::target_triple::TargetTriple;
pub(crate) use self::crate_context::*;
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct Context {
pub(crate) checksum: Option<Digest>,
pub(crate) crates: BTreeMap<CrateId, CrateContext>,
pub(crate) binary_crates: BTreeSet<CrateId>,
pub(crate) workspace_members: BTreeMap<CrateId, String>,
pub(crate) conditions: BTreeMap<String, BTreeSet<TargetTriple>>,
pub(crate) direct_deps: BTreeSet<CrateId>,
pub(crate) direct_dev_deps: BTreeSet<CrateId>,
#[serde(default)]
pub(crate) unused_patches: BTreeSet<cargo_lock::Dependency>,
}
impl Context {
pub(crate) fn try_from_path<T: AsRef<Path>>(path: T) -> Result<Self> {
let data = fs::read_to_string(path.as_ref())?;
Ok(serde_json::from_str(&data)?)
}
pub(crate) fn new(annotations: Annotations, sources_are_present: bool) -> anyhow::Result<Self> {
let crates: BTreeMap<CrateId, CrateContext> = annotations
.metadata
.crates
.values()
.map(|annotation| {
let context = CrateContext::new(
annotation,
&annotations.metadata.packages,
&annotations.lockfile.crates,
&annotations.pairred_extras,
&annotations.metadata.workspace_metadata.tree_metadata,
annotations.config.generate_binaries,
annotations.config.generate_build_scripts,
sources_are_present,
)?;
let id = CrateId::new(context.name.clone(), context.version.clone());
Ok::<_, anyhow::Error>((id, context))
})
.collect::<Result<_, _>>()?;
let binary_crates: BTreeSet<CrateId> = crates
.iter()
.filter(|(_, ctx)| ctx.targets.iter().any(|t| matches!(t, Rule::Binary(..))))
.filter(|(_, ctx)| ctx.repository.is_some())
.map(|(id, _)| id.clone())
.collect();
let conditions = resolve_cfg_platforms(
crates.values().collect(),
&annotations.config.supported_platform_triples,
)?;
let workspace_members = annotations
.metadata
.workspace_members
.iter()
.filter_map(|id| {
let pkg = &annotations.metadata.packages[id];
let package_path_id = match Self::get_package_path_id(
pkg,
&annotations.metadata.workspace_root,
&annotations.metadata.workspace_metadata.workspace_prefix,
&annotations.metadata.workspace_metadata.package_prefixes,
) {
Ok(id) => id,
Err(e) => return Some(Err(e)),
};
let crate_id = CrateId::from(pkg);
match crates[&crate_id].repository {
Some(_) => None,
None => Some(Ok((crate_id, package_path_id))),
}
})
.collect::<Result<BTreeMap<CrateId, String>>>()?;
let add_crate_ids = |crates: &mut BTreeSet<CrateId>,
deps: &Select<BTreeSet<Dependency>>| {
for dep in deps.values() {
crates.insert(CrateId::from(
&annotations.metadata.packages[&dep.package_id],
));
}
};
let mut direct_deps: BTreeSet<CrateId> = BTreeSet::new();
let mut direct_dev_deps: BTreeSet<CrateId> = BTreeSet::new();
for id in &annotations.metadata.workspace_members {
let deps = &annotations.metadata.crates[id].deps;
add_crate_ids(&mut direct_deps, &deps.normal_deps);
add_crate_ids(&mut direct_deps, &deps.proc_macro_deps);
add_crate_ids(&mut direct_deps, &deps.build_deps);
add_crate_ids(&mut direct_deps, &deps.build_link_deps);
add_crate_ids(&mut direct_deps, &deps.build_proc_macro_deps);
add_crate_ids(&mut direct_dev_deps, &deps.normal_dev_deps);
add_crate_ids(&mut direct_dev_deps, &deps.proc_macro_dev_deps);
}
let unused_patches = annotations.lockfile.unused_patches;
Ok(Self {
checksum: None,
crates,
binary_crates,
workspace_members,
conditions,
direct_dev_deps: direct_dev_deps.difference(&direct_deps).cloned().collect(),
direct_deps,
unused_patches,
})
}
fn get_package_path_id(
package: &cargo_metadata::Package,
workspace_root: &Path,
workspace_prefix: &Option<String>,
package_prefixes: &BTreeMap<String, String>,
) -> Result<String> {
let manifest_dir = package
.manifest_path
.parent()
.expect("Every manifest should have a parent")
.as_std_path();
let package_path_diff = pathdiff::diff_paths(manifest_dir, workspace_root)
.expect("Every workspace member's manifest is a child of the workspace root");
let package_path = match package_prefixes.get(&package.name) {
Some(prefix) => PathBuf::from(prefix).join(package_path_diff),
None => match workspace_prefix {
Some(prefix) => PathBuf::from(prefix).join(package_path_diff),
None => package_path_diff,
},
};
let package_path_id = package_path
.display()
.to_string()
.replace('\\', "/")
.trim_matches('/')
.to_owned();
Ok(package_path_id)
}
pub(crate) fn workspace_member_deps(&self) -> BTreeSet<CrateDependency> {
self.workspace_members
.keys()
.map(move |id| &self.crates[id])
.flat_map(|ctx| {
IntoIterator::into_iter([
&ctx.common_attrs.deps,
&ctx.common_attrs.deps_dev,
&ctx.common_attrs.proc_macro_deps,
&ctx.common_attrs.proc_macro_deps_dev,
])
.flat_map(|deps| deps.values())
.chain(
ctx.build_script_attrs
.iter()
.flat_map(|attrs| attrs.deps.values()),
)
})
.collect()
}
pub(crate) fn has_duplicate_workspace_member_dep_by_version(
&self,
dep: &CrateDependency,
) -> bool {
1 < self
.workspace_member_deps()
.into_iter()
.filter(|check| check.id.name == dep.id.name && check.id.version == dep.id.version)
.count()
}
pub(crate) fn has_duplicate_workspace_member_dep_by_alias(
&self,
dep: &CrateDependency,
) -> bool {
1 < self
.workspace_member_deps()
.into_iter()
.filter(|check| check.id.name == dep.id.name && check.alias == dep.alias)
.count()
}
pub(crate) fn has_duplicate_binary_crate(&self, bin: &CrateId) -> bool {
1 < self
.binary_crates
.iter()
.filter(|check| check.name == bin.name)
.count()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SingleBuildFileRenderContext {
pub(crate) config: Arc<RenderConfig>,
pub(crate) supported_platform_triples: Arc<BTreeSet<TargetTriple>>,
pub(crate) platform_conditions: Arc<BTreeMap<String, BTreeSet<TargetTriple>>>,
pub(crate) crate_context: Arc<CrateContext>,
}
#[cfg(test)]
mod test {
use super::*;
use camino::Utf8Path;
use semver::Version;
use crate::config::Config;
fn mock_context_common() -> Context {
let annotations = Annotations::new(
crate::test::metadata::common(),
&None,
crate::test::lockfile::common(),
Config::default(),
Utf8Path::new("/tmp/bazelworkspace"),
)
.unwrap();
Context::new(annotations, false).unwrap()
}
fn mock_context_aliases() -> Context {
let annotations = Annotations::new(
crate::test::metadata::alias(),
&None,
crate::test::lockfile::alias(),
Config::default(),
Utf8Path::new("/tmp/bazelworkspace"),
)
.unwrap();
Context::new(annotations, false).unwrap()
}
fn mock_context_workspace_build_scripts_deps() -> Context {
let annotations = Annotations::new(
crate::test::metadata::workspace_build_scripts_deps(),
&None,
crate::test::lockfile::workspace_build_scripts_deps(),
Config {
generate_build_scripts: true,
..Config::default()
},
Utf8Path::new("/tmp/bazelworkspace"),
)
.unwrap();
Context::new(annotations, false).unwrap()
}
#[test]
fn workspace_member_deps_collection() {
let context = mock_context_common();
let workspace_member_deps = context.workspace_member_deps();
assert_eq! {
workspace_member_deps
.iter()
.map(|dep| (&dep.id, context.has_duplicate_workspace_member_dep_by_alias(dep)))
.collect::<Vec<_>>(),
[
(&CrateId::new("bitflags".to_owned(), Version::new(1, 3, 2)), false),
(&CrateId::new("cfg-if".to_owned(), Version::new(1, 0, 0)), false),
],
}
}
#[test]
fn workspace_member_deps_with_aliases() {
let context = mock_context_aliases();
let workspace_member_deps = context.workspace_member_deps();
assert_eq! {
workspace_member_deps
.iter()
.map(|dep| (&dep.id, context.has_duplicate_workspace_member_dep_by_alias(dep)))
.collect::<Vec<_>>(),
[
(&CrateId::new("log".to_owned(), Version::new(0, 3, 9)), false),
(&CrateId::new("log".to_owned(), Version::new(0, 4, 21)), false),
(&CrateId::new("names".to_owned(), Version::parse("0.12.1-dev").unwrap()), false),
(&CrateId::new("names".to_owned(), Version::new(0, 13, 0)), false),
(&CrateId::new("surrealdb".to_owned(), Version::new(1, 3, 1)), false),
(&CrateId::new("value-bag".to_owned(), Version::parse("1.0.0-alpha.7").unwrap()), false),
],
}
}
#[test]
fn workspace_member_deps_contains_build_script_deps() {
let context = mock_context_workspace_build_scripts_deps();
let workspace_member_deps = context.workspace_member_deps();
assert_eq! {
workspace_member_deps
.iter()
.map(|dep| (&dep.id, context.has_duplicate_workspace_member_dep_by_alias(dep)))
.collect::<Vec<_>>(),
[
(&CrateId::new("child".to_owned(), Version::new(0, 1, 0)), false),
(&CrateId::new("tonic".to_owned(), Version::new(0, 4, 3)), false),
(&CrateId::new("tonic-build".to_owned(), Version::new(0, 4, 2)), false),
],
}
}
#[test]
fn serialization() {
let context = mock_context_aliases();
let json_text = serde_json::to_string(&context).unwrap();
let deserialized_context: Context = serde_json::from_str(&json_text).unwrap();
assert_eq!(context, deserialized_context);
}
}