blob: 50dd38c8477f68c284cfd507b78b46d5a187909b [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
use std::{
collections::{HashMap, HashSet},
fmt,
sync::Arc,
};
use fixedbitset::FixedBitSet;
use petgraph::{
dot::{self, Dot},
graph::{Neighbors, NodeIndex},
visit::Dfs,
Directed, Direction, Graph,
};
use crate::{
project::{
manifest::{self, Manifest},
File,
},
target::{Provider, Target},
Error, Result,
};
/// A node in the dependency graph.
#[derive(Debug)]
pub enum Dependency {
/// A resolved dependency.
Resolved(Arc<Target>),
/// An unresolved dependency. This occurs when a target declares a
/// dependency for a target that has not been seen.
Unresolved(String),
}
impl fmt::Display for Dependency {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Resolved(target) => write!(f, "{}", target.full_name()),
Self::Unresolved(name) => write!(f, "{name}"),
}
}
}
/// An edge in the dependency graph.
/// We define an empty struct for edges instead of using the unit type
/// to allow us to implement `fmt::Display` for it. This is needed for
/// graphviz output.
#[derive(Debug)]
pub struct Edge {}
impl fmt::Display for Edge {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "")
}
}
/// An identifier for a node in the dependency graph.
pub type NodeId = NodeIndex<u32>;
/// An iterator of target dependencies.
pub struct Dependencies<'a> {
registry: &'a Registry,
dfs: Dfs<NodeIndex, FixedBitSet>,
}
impl<'a> Iterator for Dependencies<'a> {
type Item = &'a Dependency;
fn next(&mut self) -> Option<Self::Item> {
self.dfs
.next(&self.registry.dependency_graph)
.and_then(|node_id| self.registry.dependency_graph.node_weight(node_id))
}
}
/// A database of packages known to `qg`.
#[derive(Debug)]
pub struct Registry {
/// Mapping of package slug to package. Slugs are globally unique.
targets: HashMap<String, Arc<Target>>,
/// All known package providers by name.
providers: HashMap<String, Provider>,
/// Unknown target dependencies.
unresolved_dependencies: HashSet<String>,
/// Dependency Graph of targets.
///
/// Nodes that have a `None` weight are unresolved.
dependency_graph: Graph<Dependency, Edge, Directed, u32>,
/// Map from target full name to dependency graph node IDe.
node_id_map: HashMap<String, NodeId>,
}
impl Registry {
/// Initializes an empty package registry.
#[must_use]
pub fn new() -> Self {
Self {
targets: HashMap::new(),
providers: HashMap::new(),
unresolved_dependencies: HashSet::new(),
dependency_graph: Graph::new(),
node_id_map: HashMap::new(),
}
}
/// Reads `qg` manifest files starting from the project root into a registry
/// of available packages.
///
/// # Errors
/// Returns an error if manifest files could not be read or are improperly
/// structured.
pub fn parse_manifests(root_manifest_file: &File) -> Result<Self> {
let root_manifest = root_manifest_file.deserialize_toml::<Manifest>()?;
let mut registry = Registry::new();
let project_provider = Provider::new(
&root_manifest.project.name,
root_manifest_file.path(),
false,
);
registry.add_provider(project_provider)?;
for (name, target) in root_manifest.targets {
registry.add_target(crate::Target::from_manifest(
&name,
&root_manifest.project.name,
target.namespace.is_global(),
target,
)?)?;
}
for (provider, desc) in &root_manifest.providers {
if let Some(manifest) = &desc.manifest {
let provider_file = root_manifest_file.relative_file(manifest);
let provider_data = provider_file.deserialize_toml::<manifest::ProviderFile>()?;
let global_provider = if let Some(desc) = &provider_data.provider {
desc.namespace.is_global()
} else {
false
};
registry.add_provider(Provider::new(
provider,
provider_file.path(),
global_provider,
))?;
for (name, target) in provider_data.targets {
registry.add_target(crate::Target::from_manifest(
&name,
provider,
global_provider || target.namespace.is_global(),
target,
)?)?;
}
}
}
Ok(registry)
}
/// Returns the number of registered targets.
#[must_use]
pub fn target_count(&self) -> usize {
self.targets.len()
}
/// Returns an iterator over all known targets, in arbitrary order.
pub fn targets(&self) -> impl Iterator<Item = &Target> {
self.targets.values().map(Arc::as_ref)
}
/// Registers a new provider.
pub(crate) fn add_provider(&mut self, provider: Provider) -> Result<()> {
if self.providers.contains_key(&provider.name) {
// TODO(frolv): Provider names must be unique.
return Err(Error::GenericErrorPlaceholder);
}
self.providers.insert(provider.name.clone(), provider);
Ok(())
}
/// Returns the dependency graph in graphviz dot format.
#[must_use]
pub fn dot_dependency_graph(&self) -> String {
format!(
"{:}",
Dot::with_config(&self.dependency_graph, &[dot::Config::EdgeNoLabel])
)
}
/// Inserts a target into the registry.
pub(crate) fn add_target(&mut self, target: Target) -> Result<()> {
if !self.providers.contains_key(target.provider()) {
// TODO(frolv): Target must have a known provider.
return Err(Error::GenericErrorPlaceholder);
};
let target_name = target.full_name();
for dep in target
.dependencies()
.filter(|&d| !self.targets.contains_key(d))
{
self.unresolved_dependencies.insert(dep.into());
}
self.unresolved_dependencies.remove(&target_name);
let target = Arc::new(target);
self.update_dependency_graph_for_new_target(&target);
self.targets.insert(target_name.clone(), target);
Ok(())
}
fn update_dependency_graph_for_new_target(&mut self, target: &Arc<Target>) {
let target_name = target.full_name();
let node_id = if let Some(id) = self.node_id_map.get(&target_name) {
// If the target is already in the graph, it is because a previous
// target as taken an unresolved dependency on it. Update the node
// with the resolved target.
*self
.dependency_graph
.node_weight_mut(*id)
.expect("node should exist in graph") = Dependency::Resolved(target.clone());
*id
} else {
let id = self
.dependency_graph
.add_node(Dependency::Resolved(target.clone()));
self.node_id_map.insert(target_name, id);
id
};
for dep in target.dependencies() {
// Get the node id of the dependency. Insert an unresolved
// dependency if the node does not already exist.
let dep_node_id = *self.node_id_map.entry(dep.to_string()).or_insert_with(|| {
self.dependency_graph
.add_node(Dependency::Unresolved(dep.to_string()))
});
self.dependency_graph
.add_edge(node_id, dep_node_id, Edge {});
}
}
/// Returns the build target with the specified name, if it exists.
///
/// # Errors
/// Returns an error if the named target does not exist or is an unresolved
/// dependency.
pub fn get_target(&self, name: &str) -> Result<Arc<Target>> {
self.get_node_id(name)
.and_then(|id| self.get_target_by_id(id))
}
/// Returns the node ID for a node.
///
/// # Errors
/// Returns an error if the given node name does not exist.
pub fn get_node_id(&self, name: &str) -> Result<NodeId> {
self.node_id_map
.get(name)
.ok_or(
// Node not found.
Error::GenericErrorPlaceholder,
)
.map(|id| *id)
}
/// Returns an iterator of dependencies for the node.
#[must_use]
pub fn node_deps(&self, node: NodeId) -> Neighbors<Edge, u32> {
self.dependency_graph
.neighbors_directed(node, Direction::Outgoing)
}
/// Returns the `Target` associated with the `node_id`.
///
/// # Errors
/// Returns an error if:
/// - `node_id` does not refer to a valid node in the dependency graph.
/// - the referenced node is an unresolved dependency.
pub fn get_target_by_id(&self, node_id: NodeId) -> Result<Arc<Target>> {
let target = self.dependency_graph.node_weight(node_id).ok_or(
// Node not found.
Error::GenericErrorPlaceholder,
)?;
match target {
Dependency::Resolved(target) => Ok(target.clone()),
Dependency::Unresolved(_) => Err(Error::GenericErrorPlaceholder),
}
}
/// Returns an iterator for traversing a target's transitive dependencies.
///
/// # Errors
/// Returns an error if `target_name` does not name a valid target.
#[allow(unused)]
pub fn transitive_dependencies(&self, target_name: &str) -> Result<Dependencies> {
let node_id = self.get_node_id(target_name)?;
let dfs = Dfs::new(&self.dependency_graph, node_id);
Ok(Dependencies {
registry: self,
dfs,
})
}
}
impl Default for Registry {
fn default() -> Self {
Self::new()
}
}