use super::git_sha::GitSha;
use super::manifest::{GeneratedAt, Manifest, ManifestContent, ManifestProvenance};
use super::naming::ArtefactName;
use super::packaging_error::PackagingError;
use super::schema_version::SchemaVersion;
use super::sha256_digest::Sha256Digest;
use super::target::TargetTriple;
use super::toolchain_channel::ToolchainChannel;
use sha2::{Digest, Sha256};
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct PackageParams {
pub git_sha: GitSha,
pub toolchain: ToolchainChannel,
pub target: TargetTriple,
pub library_files: Vec<PathBuf>,
pub output_dir: PathBuf,
pub generated_at: GeneratedAt,
}
#[derive(Debug)]
pub struct PackageOutput {
pub archive_path: PathBuf,
pub manifest: Manifest,
}
pub fn compute_sha256(path: &Path) -> Result<Sha256Digest, PackagingError> {
let mut file = fs::File::open(path)?;
let mut hasher = Sha256::new();
let mut buffer = [0u8; 8192];
loop {
let bytes_read = file.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
let hex = format!("{:x}", hasher.finalize());
Ok(Sha256Digest::try_from(hex)?)
}
pub fn create_archive(
output_path: &Path,
files: &[(PathBuf, String)],
) -> Result<(), PackagingError> {
let output_file = fs::File::create(output_path)?;
let zstd_encoder = zstd::Encoder::new(output_file, 0)?.auto_finish();
let mut archive = tar::Builder::new(zstd_encoder);
for (source_path, archive_name) in files {
archive.append_path_with_name(source_path, archive_name)?;
}
archive.finish()?;
Ok(())
}
pub fn generate_manifest_json(manifest: &Manifest) -> Result<String, PackagingError> {
Ok(serde_json::to_string_pretty(manifest)?)
}
pub fn package_artefact(params: PackageParams) -> Result<PackageOutput, PackagingError> {
if params.library_files.is_empty() {
return Err(PackagingError::EmptyFileList);
}
let artefact_name = ArtefactName::new(
params.git_sha.clone(),
params.toolchain.clone(),
params.target.clone(),
);
let archive_path = params.output_dir.join(artefact_name.filename());
let file_names = collect_file_names(¶ms.library_files)?;
let lib_entries: Vec<(PathBuf, String)> = params
.library_files
.iter()
.zip(&file_names)
.map(|(p, n)| (p.clone(), n.clone()))
.collect();
create_archive(&archive_path, &lib_entries)?;
let digest = compute_sha256(&archive_path)?;
let manifest = build_manifest(¶ms, &file_names, &digest);
Ok(PackageOutput {
archive_path,
manifest,
})
}
fn collect_file_names(paths: &[PathBuf]) -> Result<Vec<String>, PackagingError> {
paths
.iter()
.map(|p| {
p.file_name()
.map(|n| n.to_string_lossy().into_owned())
.ok_or_else(|| PackagingError::InvalidLibraryPath(p.clone()))
})
.collect()
}
fn build_manifest(
params: &PackageParams,
file_names: &[String],
sha256: &Sha256Digest,
) -> Manifest {
let provenance = ManifestProvenance {
git_sha: params.git_sha.clone(),
schema_version: SchemaVersion::current(),
toolchain: params.toolchain.clone(),
target: params.target.clone(),
};
let content = ManifestContent {
generated_at: params.generated_at.clone(),
files: file_names.to_vec(),
sha256: sha256.clone(),
};
Manifest::new(provenance, content)
}
#[cfg(test)]
#[path = "packaging_tests.rs"]
mod tests;