#![doc = include_str!("../README.md")]
pub mod build;
pub mod buildpack_dependency_graph;
pub mod buildpack_kind;
pub mod cargo;
pub mod cross_compile;
pub mod dependency_graph;
pub mod output;
pub mod package;
pub mod package_descriptor;
pub mod util;
use crate::build::BuildpackBinaries;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CargoProfile {
Dev,
Release,
}
fn assemble_buildpack_directory(
destination_path: impl AsRef<Path>,
buildpack_descriptor_path: impl AsRef<Path>,
buildpack_binaries: &BuildpackBinaries,
) -> std::io::Result<()> {
fs::create_dir_all(destination_path.as_ref())?;
fs::copy(
buildpack_descriptor_path.as_ref(),
destination_path.as_ref().join("buildpack.toml"),
)?;
let bin_path = destination_path.as_ref().join("bin");
fs::create_dir_all(&bin_path)?;
fs::copy(
&buildpack_binaries.buildpack_target_binary_path,
bin_path.join("build"),
)?;
create_file_symlink("build", bin_path.join("detect"))?;
if !buildpack_binaries.additional_target_binary_paths.is_empty() {
let additional_binaries_dir = destination_path
.as_ref()
.join(".libcnb-cargo")
.join("additional-bin");
fs::create_dir_all(&additional_binaries_dir)?;
for (binary_target_name, binary_path) in &buildpack_binaries.additional_target_binary_paths
{
fs::copy(
binary_path,
additional_binaries_dir.join(binary_target_name),
)?;
}
}
Ok(())
}
#[cfg(target_family = "unix")]
fn create_file_symlink<P: AsRef<Path>, Q: AsRef<Path>>(
original: P,
link: Q,
) -> std::io::Result<()> {
std::os::unix::fs::symlink(original.as_ref(), link.as_ref())
}
#[cfg(target_family = "windows")]
fn create_file_symlink<P: AsRef<Path>, Q: AsRef<Path>>(
original: P,
link: Q,
) -> std::io::Result<()> {
std::os::windows::fs::symlink_file(original.as_ref(), link.as_ref())
}
pub fn find_buildpack_dirs(start_dir: &Path) -> Result<Vec<PathBuf>, ignore::Error> {
ignore::Walk::new(start_dir)
.collect::<Result<Vec<_>, _>>()
.map(|entries| {
entries
.iter()
.filter_map(|entry| {
if entry.path().is_dir() && entry.path().join("buildpack.toml").exists() {
Some(entry.path().to_path_buf())
} else {
None
}
})
.collect()
})
}
pub fn find_cargo_workspace_root_dir(
dir_in_workspace: &Path,
) -> Result<PathBuf, FindCargoWorkspaceRootError> {
let cargo_bin = std::env::var("CARGO")
.map(PathBuf::from)
.map_err(FindCargoWorkspaceRootError::GetCargoEnv)?;
let output = Command::new(cargo_bin)
.args(["locate-project", "--workspace", "--message-format", "plain"])
.current_dir(dir_in_workspace)
.output()
.map_err(FindCargoWorkspaceRootError::SpawnCommand)?;
let status = output.status;
output
.status
.success()
.then_some(output)
.ok_or(FindCargoWorkspaceRootError::CommandFailure(status))
.and_then(|output| {
let root_cargo_toml = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
root_cargo_toml.parent().map(Path::to_path_buf).ok_or(
FindCargoWorkspaceRootError::GetParentDirectory(root_cargo_toml),
)
})
}
#[derive(thiserror::Error, Debug)]
pub enum FindCargoWorkspaceRootError {
#[error("Couldn't get value of CARGO environment variable: {0}")]
GetCargoEnv(#[source] std::env::VarError),
#[error("Error while spawning Cargo process: {0}")]
SpawnCommand(#[source] std::io::Error),
#[error("Unexpected Cargo exit status ({}) while attempting to read workspace root", exit_code_or_unknown(*.0))]
CommandFailure(std::process::ExitStatus),
#[error("Couldn't locate a Cargo workspace within {0} or its parent directories")]
GetParentDirectory(PathBuf),
}
fn exit_code_or_unknown(exit_status: std::process::ExitStatus) -> String {
exit_status
.code()
.map_or_else(|| String::from("<unknown>"), |code| code.to_string())
}