libcnb_package/
package.rs

1use crate::build::build_buildpack_binaries;
2use crate::buildpack_kind::{BuildpackKind, determine_buildpack_kind};
3use crate::package_descriptor::{NormalizePackageDescriptorError, normalize_package_descriptor};
4use crate::{CargoProfile, assemble_buildpack_directory};
5use cargo_metadata::MetadataCommand;
6use libcnb_common::toml_file::{TomlFileError, read_toml_file, write_toml_file};
7use libcnb_data::buildpack::BuildpackId;
8use libcnb_data::package_descriptor::PackageDescriptor;
9use std::collections::BTreeMap;
10use std::ffi::OsString;
11use std::fs;
12use std::path::{Path, PathBuf};
13
14/// Packages either a libcnb.rs or a composite buildpack.
15///
16/// # Errors
17///
18/// Returns `Err` if packaging failed or the given buildpack directory is unsupported.
19pub fn package_buildpack(
20    buildpack_directory: &Path,
21    cargo_profile: CargoProfile,
22    target_triple: &str,
23    cargo_build_env: &[(OsString, OsString)],
24    destination: &Path,
25    dependencies: &BTreeMap<BuildpackId, PathBuf>,
26) -> Result<(), PackageBuildpackError> {
27    match determine_buildpack_kind(buildpack_directory) {
28        Some(BuildpackKind::LibCnbRs) => package_libcnb_buildpack(
29            buildpack_directory,
30            cargo_profile,
31            target_triple,
32            cargo_build_env,
33            destination,
34        )
35        .map_err(PackageBuildpackError::PackageLibcnbBuildpackError),
36        Some(BuildpackKind::Composite) => {
37            package_composite_buildpack(buildpack_directory, destination, dependencies)
38                .map_err(PackageBuildpackError::PackageCompositeBuildpackError)
39        }
40        _ => Err(PackageBuildpackError::UnsupportedBuildpack),
41    }
42}
43
44#[derive(thiserror::Error, Debug)]
45pub enum PackageBuildpackError {
46    #[error(transparent)]
47    PackageCompositeBuildpackError(PackageCompositeBuildpackError),
48    #[error(transparent)]
49    PackageLibcnbBuildpackError(PackageLibcnbBuildpackError),
50    #[error("Buildpack is not supported to be packaged")]
51    UnsupportedBuildpack,
52}
53
54/// Packages a libcnb.rs buildpack after (cross-) compiling.
55///
56/// # Errors
57///
58/// Returns `Err` if compilation or packaging failed.
59fn package_libcnb_buildpack(
60    buildpack_directory: &Path,
61    cargo_profile: CargoProfile,
62    target_triple: &str,
63    cargo_build_env: &[(OsString, OsString)],
64    destination: &Path,
65) -> Result<(), PackageLibcnbBuildpackError> {
66    let cargo_metadata = MetadataCommand::new()
67        .manifest_path(buildpack_directory.join("Cargo.toml"))
68        .exec()
69        .map_err(PackageLibcnbBuildpackError::CargoMetadataError)?;
70
71    let buildpack_binaries = build_buildpack_binaries(
72        buildpack_directory,
73        &cargo_metadata,
74        cargo_profile,
75        cargo_build_env,
76        target_triple,
77    )
78    .map_err(PackageLibcnbBuildpackError::BuildBinariesError)?;
79
80    assemble_buildpack_directory(
81        destination,
82        buildpack_directory.join("buildpack.toml"),
83        &buildpack_binaries,
84    )
85    .map_err(PackageLibcnbBuildpackError::AssembleBuildpackDirectory)?;
86
87    fs::write(
88        destination.join("package.toml"),
89        "[buildpack]\nuri = \".\"\n",
90    )
91    .map_err(PackageLibcnbBuildpackError::WritePackageDescriptor)
92}
93
94#[derive(thiserror::Error, Debug)]
95pub enum PackageLibcnbBuildpackError {
96    #[error("Assembling buildpack directory failed: {0}")]
97    AssembleBuildpackDirectory(std::io::Error),
98    #[error("Couldn't write package.toml: {0}")]
99    WritePackageDescriptor(std::io::Error),
100    #[error("Building buildpack binaries failed: {0}")]
101    BuildBinariesError(crate::build::BuildBinariesError),
102    #[error("Obtaining Cargo metadata failed: {0}")]
103    CargoMetadataError(cargo_metadata::Error),
104}
105
106/// Packages a composite buildpack.
107///
108/// Packaging consists of copying `buildpack.toml` as well as `package.toml` to the given
109/// destination path.
110///
111/// In addition, references to libcnb.rs buildpacks in the form of `libcnb:` URIs are resolved and
112/// local paths are absolutized so the `package.toml` stays correct after being moved to a
113/// different location.
114///
115/// # Errors
116///
117/// Returns `Err` if a `libcnb:` URI refers to a buildpack not in `buildpack_paths` or packaging
118/// otherwise failed (i.e. I/O errors).
119pub fn package_composite_buildpack(
120    buildpack_directory: &Path,
121    destination: &Path,
122    buildpack_paths: &BTreeMap<BuildpackId, PathBuf>,
123) -> Result<(), PackageCompositeBuildpackError> {
124    fs::copy(
125        buildpack_directory.join("buildpack.toml"),
126        destination.join("buildpack.toml"),
127    )
128    .map_err(PackageCompositeBuildpackError::CouldNotCopyBuildpackToml)?;
129
130    let package_descriptor_path = buildpack_directory.join("package.toml");
131
132    let normalized_package_descriptor =
133        read_toml_file::<PackageDescriptor>(&package_descriptor_path)
134            .map_err(PackageCompositeBuildpackError::CouldNotReadPackageDescriptor)
135            .and_then(|package_descriptor| {
136                normalize_package_descriptor(
137                    &package_descriptor,
138                    &package_descriptor_path,
139                    buildpack_paths,
140                )
141                .map_err(PackageCompositeBuildpackError::NormalizePackageDescriptorError)
142            })?;
143
144    write_toml_file(
145        &normalized_package_descriptor,
146        destination.join("package.toml"),
147    )
148    .map_err(PackageCompositeBuildpackError::CouldNotWritePackageDescriptor)
149}
150
151#[derive(thiserror::Error, Debug)]
152pub enum PackageCompositeBuildpackError {
153    #[error("Couldn't copy buildpack.toml: {0}")]
154    CouldNotCopyBuildpackToml(std::io::Error),
155    #[error("Couldn't read package.toml: {0}")]
156    CouldNotReadPackageDescriptor(TomlFileError),
157    #[error("Error while normalizing package.toml: {0}")]
158    NormalizePackageDescriptorError(NormalizePackageDescriptorError),
159    #[error("Couldn't write package.toml: {0}")]
160    CouldNotWritePackageDescriptor(TomlFileError),
161}