libcnb_package/
buildpack_dependency_graph.rs

1use crate::buildpack_kind::BuildpackKind;
2use crate::buildpack_kind::determine_buildpack_kind;
3use crate::dependency_graph::{
4    CreateDependencyGraphError, DependencyNode, create_dependency_graph,
5};
6use crate::find_buildpack_dirs;
7use crate::package_descriptor::buildpack_id_from_libcnb_dependency;
8use libcnb_common::toml_file::{TomlFileError, read_toml_file};
9use libcnb_data::buildpack::{BuildpackDescriptor, BuildpackId, BuildpackIdError};
10use libcnb_data::package_descriptor::PackageDescriptor;
11use petgraph::Graph;
12use std::convert::Infallible;
13use std::path::{Path, PathBuf};
14
15/// Creates a dependency graph of libcnb.rs and composite buildpacks in a directory.
16///
17/// Buildpacks that aren't implemented with libcnb.rs or aren't composite buildpacks will not be part
18/// of the dependency graph. Examples buildpacks that are not included are docker image URLs or
19/// directories containing CNBs written in bash.
20///
21/// Likewise, the only dependency edges in the resulting graph are dependencies declared via
22/// `libcnb:` URIs.
23///
24/// # Errors
25///
26/// Returns `Err` if a buildpack declares an invalid dependency, has an invalid buildpack.toml or
27/// package.toml or an I/O error occurred while traversing the given directory.
28pub fn build_libcnb_buildpacks_dependency_graph(
29    cargo_workspace_root: &Path,
30) -> Result<Graph<BuildpackDependencyGraphNode, ()>, BuildBuildpackDependencyGraphError> {
31    find_buildpack_dirs(cargo_workspace_root)
32        .map_err(BuildBuildpackDependencyGraphError::FindBuildpackDirectories)
33        .and_then(|buildpack_directories| {
34            buildpack_directories
35                .iter()
36                .filter(|buildpack_directory| {
37                    matches!(
38                        determine_buildpack_kind(buildpack_directory),
39                        Some(BuildpackKind::LibCnbRs | BuildpackKind::Composite)
40                    )
41                })
42                .map(|buildpack_directory| {
43                    build_libcnb_buildpack_dependency_graph_node(buildpack_directory)
44                })
45                .collect::<Result<Vec<_>, _>>()
46        })
47        .and_then(|nodes| {
48            create_dependency_graph(nodes)
49                .map_err(BuildBuildpackDependencyGraphError::CreateDependencyGraphError)
50        })
51}
52
53fn build_libcnb_buildpack_dependency_graph_node(
54    buildpack_directory: &Path,
55) -> Result<BuildpackDependencyGraphNode, BuildBuildpackDependencyGraphError> {
56    let buildpack_id =
57        read_toml_file::<BuildpackDescriptor>(buildpack_directory.join("buildpack.toml"))
58            .map_err(BuildBuildpackDependencyGraphError::ReadBuildpackDescriptorError)
59            .map(|buildpack_descriptor| buildpack_descriptor.buildpack().id.clone())?;
60
61    let package_toml_path = buildpack_directory.join("package.toml");
62    let dependencies = if package_toml_path.is_file() {
63        read_toml_file::<PackageDescriptor>(package_toml_path)
64            .map_err(BuildBuildpackDependencyGraphError::ReadPackageDescriptorError)
65            .and_then(|package_descriptor| {
66                get_buildpack_dependencies(&package_descriptor)
67                    .map_err(BuildBuildpackDependencyGraphError::InvalidDependencyBuildpackId)
68            })?
69    } else {
70        Vec::new()
71    };
72
73    Ok(BuildpackDependencyGraphNode {
74        buildpack_id,
75        path: PathBuf::from(buildpack_directory),
76        dependencies,
77    })
78}
79
80#[derive(thiserror::Error, Debug)]
81pub enum BuildBuildpackDependencyGraphError {
82    #[error("Error while finding buildpack directories: {0}")]
83    FindBuildpackDirectories(ignore::Error),
84    #[error("Couldn't read buildpack.toml: {0}")]
85    ReadBuildpackDescriptorError(TomlFileError),
86    #[error("Couldn't read package.toml: {0}")]
87    ReadPackageDescriptorError(TomlFileError),
88    #[error("Dependency uses an invalid buildpack id: {0}")]
89    InvalidDependencyBuildpackId(BuildpackIdError),
90    #[error("Error while creating dependency graph: {0}")]
91    CreateDependencyGraphError(CreateDependencyGraphError<BuildpackId, Infallible>),
92}
93
94#[derive(Debug)]
95pub struct BuildpackDependencyGraphNode {
96    pub buildpack_id: BuildpackId,
97    pub path: PathBuf,
98    pub dependencies: Vec<BuildpackId>,
99}
100
101impl DependencyNode<BuildpackId, Infallible> for BuildpackDependencyGraphNode {
102    fn id(&self) -> BuildpackId {
103        self.buildpack_id.clone()
104    }
105
106    fn dependencies(&self) -> Result<Vec<BuildpackId>, Infallible> {
107        Ok(self.dependencies.clone())
108    }
109}
110
111fn get_buildpack_dependencies(
112    package_descriptor: &PackageDescriptor,
113) -> Result<Vec<BuildpackId>, BuildpackIdError> {
114    package_descriptor
115        .dependencies
116        .iter()
117        .filter_map(|dependency| buildpack_id_from_libcnb_dependency(dependency).transpose())
118        .collect()
119}