blob: 7c50fcd677933e4e30c6c9f291c8d47604fca6ae [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::{target::GnTarget, types::*},
anyhow::{anyhow, Context as _, Result},
cargo_metadata::{DependencyKind, Metadata, Package, PackageId},
std::collections::{HashMap, HashSet},
};
pub struct GnBuildGraph<'a> {
metadata: &'a Metadata,
// hashset because the same target can get added multiple times
targets: HashSet<GnTarget<'a>>,
}
impl<'a> GnBuildGraph<'a> {
pub fn new(metadata: &'a Metadata) -> Self {
GnBuildGraph { metadata, targets: HashSet::new() }
}
pub fn targets(&'a self) -> impl Iterator<Item = &'a GnTarget<'_>> {
self.targets.iter()
}
pub fn find_library_target(&self, package: &str, version: &str) -> Option<&GnTarget<'_>> {
self.targets().find(|t| match t.target_type {
GnRustType::Library
| GnRustType::Rlib
| GnRustType::Staticlib
| GnRustType::Dylib
| GnRustType::Cdylib
| GnRustType::ProcMacro => t.pkg_name == package && t.version() == version,
_ => false,
})
}
pub fn find_binary_target(
&self,
package: &str,
version: &str,
target: &str,
) -> Option<&GnTarget<'_>> {
self.targets().find(|t| match t.target_type {
GnRustType::Binary => {
t.pkg_name == package && t.version() == version && t.target_name == target
}
_ => false,
})
}
/// Add a cargo package to the target list. If the dependencies
/// are not already in the target graph, add them as well
pub fn add_cargo_package(&mut self, cargo_pkg_id: PackageId) -> Result<()> {
let package = &self.metadata[&cargo_pkg_id];
for node in self
.metadata
.resolve
.as_ref()
.unwrap()
.nodes
.iter()
.filter(|node| node.id == cargo_pkg_id)
{
let mut dependencies = HashMap::<Option<Platform>, Vec<(&'a Package, String)>>::new();
// collect the dependency edges for this node
for node_dep in node.deps.iter() {
let id = &node_dep.pkg;
if node_dep.dep_kinds.is_empty() {
return Err(anyhow!("Must use rustc 1.41+ to use cargo-gnaw. Needs dependency kind information."));
}
for kinds in &node_dep.dep_kinds {
let kind = &kinds.kind;
let target = &kinds.target;
match kind {
DependencyKind::Normal => {
self.add_cargo_package(id.clone())?;
let platform = target.as_ref().map(|x| format!("{}", x));
let platform_deps = dependencies.entry(platform).or_default();
// Somehow we get duplicates of the same dependency
// from cargo on some targets. Filter those out
// here. This is technically quadratic, but that's
// unlikely to matter for the number of deps on a
// single crate.
if !platform_deps.iter().any(|dep| dep.1 == node_dep.name.clone()) {
platform_deps.push((&self.metadata[id], node_dep.name.clone()));
}
}
DependencyKind::Build => {}
DependencyKind::Development => {}
err => {
return Err(anyhow!(
"Don't know how to handle this kind of dependency edge: {:?}",
err
))
}
}
}
}
let has_build_script = package.targets.iter().any(|target| {
for kind in &target.kind {
let gn_rust_type = GnRustType::try_from(kind.as_str())
.with_context(|| {
format!("Failed to resolve GN target type for: {:?}", target)
})
.unwrap();
if gn_rust_type == GnRustType::BuildScript {
return true;
}
}
false
});
for rust_target in package.targets.iter() {
for kind in &rust_target.kind {
let target_type = GnRustType::try_from(kind.as_str())?;
match target_type {
GnRustType::Library | GnRustType::Rlib | GnRustType::ProcMacro => {
let gn_target = GnTarget::new(
&node.id,
&rust_target.name,
&package.name,
&package.edition,
&rust_target.src_path,
&package.version,
target_type,
node.features.as_slice(),
has_build_script,
dependencies.clone(),
);
self.targets.insert(gn_target);
}
GnRustType::Binary => {
// If a crate contains both a library and binary targets,
// cargo implicitly adds a dependency from each binary to the library.
//
// This logic imitates that behaviour.
let mut deps = dependencies.clone();
let maybe_library = package.targets.iter().find(|v| {
v.kind.iter().any(|kind| {
matches!(
GnRustType::try_from(kind.as_str()),
Ok(GnRustType::Library)
)
})
});
if let Some(lib) = maybe_library {
deps.entry(None).or_default().push((
&self.metadata[&node.id],
lib.name.clone().replace('-', "_"),
));
}
let gn_target = GnTarget::new(
&node.id,
&rust_target.name,
&package.name,
&package.edition,
&rust_target.src_path,
&package.version,
target_type,
node.features.as_slice(),
has_build_script,
deps,
);
self.targets.insert(gn_target);
}
// FIXME(https://fxbug.dev/42173393): support staticlib, dylib, and
// cdylib crate types.
GnRustType::Staticlib | GnRustType::Dylib | GnRustType::Cdylib => (),
// BuildScripts are handled as part of the targets
GnRustType::BuildScript => (),
// TODO support building tests. Should integrate with whatever GN
// metadata collection system used by the main build.
GnRustType::Example | GnRustType::Bench | GnRustType::Test => (),
}
}
}
}
Ok(())
}
}