use crate::dependency_binaries::{DependencyBinary, binary_filename, provenance_filename};
pub use crate::installer_packaging::{ArchiveFormat, TargetTriple};
use std::fs;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Debug)]
pub struct DependencyPackageParams {
pub dependency: DependencyBinary,
pub target: TargetTriple,
pub binary_path: PathBuf,
pub output_dir: PathBuf,
}
#[derive(Debug)]
pub struct DependencyPackageOutput {
pub archive_path: PathBuf,
pub archive_name: String,
}
#[derive(Debug, Error)]
pub enum DependencyPackagingError {
#[error("I/O error during packaging: {0}")]
Io(#[from] std::io::Error),
#[error("binary file not found: {0}")]
BinaryNotFound(PathBuf),
#[error("ZIP archive error: {0}")]
Zip(#[from] zip::result::ZipError),
}
#[must_use]
pub fn inner_dir_name(dependency: &DependencyBinary, target: &TargetTriple) -> String {
format!(
"{}-{}-v{}",
dependency.package(),
target.as_str(),
dependency.version()
)
}
#[must_use]
pub fn archive_format(target: &TargetTriple) -> ArchiveFormat {
if target.is_windows() {
ArchiveFormat::Zip
} else {
ArchiveFormat::Tgz
}
}
pub fn package_dependency_binary(
params: DependencyPackageParams,
) -> Result<DependencyPackageOutput, DependencyPackagingError> {
if !params.binary_path.is_file() {
return Err(DependencyPackagingError::BinaryNotFound(
params.binary_path.clone(),
));
}
fs::create_dir_all(¶ms.output_dir)?;
let archive_name =
crate::dependency_binaries::archive_filename(¶ms.dependency, ¶ms.target);
let archive_path = params.output_dir.join(&archive_name);
let inner_dir = inner_dir_name(¶ms.dependency, ¶ms.target);
let binary_name = binary_filename(¶ms.dependency, ¶ms.target);
match archive_format(¶ms.target) {
ArchiveFormat::Tgz => {
create_tgz_archive(&archive_path, &inner_dir, ¶ms.binary_path, &binary_name)?;
}
ArchiveFormat::Zip => {
create_zip_archive(&archive_path, &inner_dir, ¶ms.binary_path, &binary_name)?;
}
}
let archive_path = archive_path.canonicalize()?;
Ok(DependencyPackageOutput {
archive_path,
archive_name,
})
}
#[must_use]
pub fn render_provenance_markdown(dependencies: &[DependencyBinary]) -> String {
let mut output = String::from("# Dependency binary licences and provenance\n\n");
output.push_str(
"Whitaker publishes the following third-party dependency binaries from repository releases.\n\n",
);
for dependency in dependencies {
output.push_str(&format!("## {}\n\n", dependency.package()));
output.push_str(&format!("- Binary: `{}`\n", dependency.binary()));
output.push_str(&format!("- Version: `{}`\n", dependency.version()));
output.push_str(&format!("- Licence: `{}`\n", dependency.license()));
output.push_str(&format!("- Repository: {}\n\n", dependency.repository()));
}
output
}
pub fn write_provenance_markdown(
output_dir: &Path,
dependencies: &[DependencyBinary],
) -> Result<PathBuf, DependencyPackagingError> {
fs::create_dir_all(output_dir)?;
let path = output_dir.join(provenance_filename());
fs::write(&path, render_provenance_markdown(dependencies))?;
Ok(path)
}
fn create_tgz_archive(
output_path: &Path,
inner_dir: &str,
binary_path: &Path,
binary_name: &str,
) -> Result<(), DependencyPackagingError> {
let output_file = fs::File::create(output_path)?;
let gz_encoder = flate2::write::GzEncoder::new(output_file, flate2::Compression::default());
let mut archive = tar::Builder::new(gz_encoder);
archive.mode(tar::HeaderMode::Deterministic);
archive.append_path_with_name(binary_path, format!("{inner_dir}/{binary_name}"))?;
let gz_encoder = archive.into_inner()?;
gz_encoder.finish()?;
Ok(())
}
fn create_zip_archive(
output_path: &Path,
inner_dir: &str,
binary_path: &Path,
binary_name: &str,
) -> Result<(), DependencyPackagingError> {
let output_file = fs::File::create(output_path)?;
let mut zip_writer = zip::ZipWriter::new(output_file);
let options = zip::write::SimpleFileOptions::default()
.last_modified_time(zip::DateTime::default())
.compression_method(zip::CompressionMethod::Deflated);
zip_writer.start_file(format!("{inner_dir}/{binary_name}"), options)?;
let mut binary_file = fs::File::open(binary_path)?;
let mut buffer = [0u8; 8_192];
loop {
let bytes_read = binary_file.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
zip_writer.write_all(&buffer[..bytes_read])?;
}
zip_writer.finish()?;
Ok(())
}