use std::collections::BTreeMap;
use std::env;
use std::fs;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
const SCHEMA: &str = "oliphaunt-artifact-manifest-v1";
const PRODUCT: &str = "oliphaunt-extension-postgis";
const VERSION: &str = env!("CARGO_PKG_VERSION");
const KIND: &str = "extension";
const TARGET: &str = "aarch64-unknown-linux-gnu";
const RUNTIME_PRODUCT: &str = "liboliphaunt-native";
const RUNTIME_VERSION: &str = "0.2.0";
const EXTENSIONS: &[&str] = &[
"postgis",
];
const EXTENSION_DEPENDENCIES: &[(&str, &[&str])] = &[
("postgis", &[]),
];
const PART_ROOTS: &[&str] = &[
oliphaunt_extension_postgis_linux_arm64_gnu_part_001::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_002::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_003::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_004::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_005::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_006::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_007::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_008::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_009::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_010::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_011::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_012::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_013::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_014::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_015::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_016::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_017::PAYLOAD_ROOT,
oliphaunt_extension_postgis_linux_arm64_gnu_part_018::PAYLOAD_ROOT,
];
fn main() {
emit_manifest();
}
fn emit_manifest() {
let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR is set"));
let payload = out_dir.join("payload");
if payload.exists() {
fs::remove_dir_all(&payload).expect("remove stale Oliphaunt extension payload");
}
fs::create_dir_all(&payload).expect("create Oliphaunt extension payload directory");
let part_roots = part_roots();
if part_roots.is_empty() {
if env::var_os("OLIPHAUNT_ARTIFACT_CRATE_REQUIRE_PAYLOAD").is_some() {
panic!("missing Oliphaunt extension payload part crates");
}
return;
}
let mut chunk_files: BTreeMap<String, Vec<(usize, PathBuf)>> = BTreeMap::new();
for root in part_roots {
println!("cargo::rerun-if-changed={}", root.display());
copy_complete_files(&root.join("files"), &payload).expect("copy complete extension payload files");
collect_chunks(&root.join("chunks"), &root.join("chunks"), &mut chunk_files)
.expect("collect extension payload chunks");
}
for (relative, mut chunks) in chunk_files {
chunks.sort_by_key(|(index, _)| *index);
for (expected, (actual, _)) in chunks.iter().enumerate() {
if *actual != expected {
panic!("non-contiguous Oliphaunt extension chunk indexes for {relative}");
}
}
let output = payload.join(&relative);
if let Some(parent) = output.parent() {
fs::create_dir_all(parent).expect("create reconstructed extension file parent");
}
let mut writer = fs::File::create(&output).expect("create reconstructed extension payload file");
for (_, path) in chunks {
let mut reader = fs::File::open(&path).expect("open extension payload chunk");
io::copy(&mut reader, &mut writer).expect("append extension payload chunk");
}
}
let files = collect_files(&payload).expect("collect reconstructed extension payload files");
if files.is_empty() {
panic!("Oliphaunt extension payload part crates produced no files");
}
let manifest = out_dir.join("oliphaunt-artifact.toml");
let mut text = format!(
"schema = {SCHEMA:?}\nproduct = {PRODUCT:?}\nversion = {VERSION:?}\nkind = {KIND:?}\ntarget = {TARGET:?}\nruntime-product = {RUNTIME_PRODUCT:?}\nruntime-version = {RUNTIME_VERSION:?}\n"
);
if SCHEMA == "oliphaunt-artifact-manifest-v1" {
if EXTENSIONS.len() != 1 {
panic!("v1 extension manifest requires exactly one member");
}
text.push_str(&format!("extension = {:?}\n", EXTENSIONS[0]));
append_dependencies(&mut text, EXTENSIONS[0]);
append_manifest_files(&mut text, &payload, "[[files]]");
} else if SCHEMA == "oliphaunt-artifact-manifest-v2" {
let extensions_root = payload.join("extensions");
let actual_members = directory_names(&extensions_root).expect("read reconstructed extension bundle members");
let expected_members: Vec<String> = EXTENSIONS.iter().map(|value| (*value).to_owned()).collect();
if actual_members != expected_members {
panic!("reconstructed extension bundle member set mismatch: expected {expected_members:?}, got {actual_members:?}");
}
for extension in EXTENSIONS {
text.push_str(&format!("\n[[extensions]]\nextension = {extension:?}\n"));
append_dependencies(&mut text, extension);
append_manifest_files(&mut text, &extensions_root.join(extension), "[[extensions.files]]");
}
} else {
panic!("unsupported extension artifact manifest schema {SCHEMA}");
}
fs::write(&manifest, text).expect("write Oliphaunt extension artifact manifest");
println!("cargo::metadata=manifest={}", manifest.display());
}
fn append_dependencies(text: &mut String, extension: &str) {
let dependencies = EXTENSION_DEPENDENCIES.iter()
.find(|(candidate, _)| *candidate == extension)
.map(|(_, dependencies)| *dependencies)
.unwrap_or_else(|| panic!("missing dependency metadata for extension {extension}"));
text.push_str(&format!("dependencies = {dependencies:?}\n"));
}
fn append_manifest_files(text: &mut String, root: &Path, table: &str) {
let files = collect_files(root).expect("collect extension member payload files");
if files.is_empty() {
panic!("Oliphaunt extension member payload produced no files under {}", root.display());
}
for file in files {
let relative = file.strip_prefix(root)
.expect("payload file stays under member root")
.to_string_lossy()
.replace(std::path::MAIN_SEPARATOR, "/");
let sha256 = sha256_file(&file).expect("hash extension payload file");
text.push_str(&format!(
"\n{table}\nsource = {:?}\nrelative = {:?}\nsha256 = {:?}\nexecutable = false\n",
file.display().to_string(), relative, sha256,
));
}
}
fn directory_names(root: &Path) -> io::Result<Vec<String>> {
let mut names = Vec::new();
for entry in fs::read_dir(root)? {
let entry = entry?;
if entry.file_type()?.is_dir() {
names.push(entry.file_name().to_string_lossy().into_owned());
}
}
names.sort();
Ok(names)
}
fn part_roots() -> Vec<PathBuf> {
PART_ROOTS.iter().map(PathBuf::from).collect()
}
fn copy_complete_files(source: &Path, destination: &Path) -> io::Result<()> {
if !source.is_dir() {
return Ok(());
}
for entry in fs::read_dir(source)? {
let entry = entry?;
let path = entry.path();
let output = destination.join(path.strip_prefix(source).unwrap_or(&path));
copy_tree_entry(&path, &output)?;
}
Ok(())
}
fn copy_tree_entry(source: &Path, destination: &Path) -> io::Result<()> {
let metadata = fs::metadata(source)?;
if metadata.is_dir() {
fs::create_dir_all(destination)?;
for entry in fs::read_dir(source)? {
let entry = entry?;
copy_tree_entry(&entry.path(), &destination.join(entry.file_name()))?;
}
} else if metadata.is_file() {
if let Some(parent) = destination.parent() {
fs::create_dir_all(parent)?;
}
fs::copy(source, destination)?;
}
Ok(())
}
fn collect_chunks(
root: &Path,
current: &Path,
chunks: &mut BTreeMap<String, Vec<(usize, PathBuf)>>,
) -> io::Result<()> {
if !current.is_dir() {
return Ok(());
}
for entry in fs::read_dir(current)? {
let entry = entry?;
let path = entry.path();
let metadata = fs::metadata(&path)?;
if metadata.is_dir() {
collect_chunks(root, &path, chunks)?;
continue;
}
if !metadata.is_file() {
continue;
}
let relative = path.strip_prefix(root).unwrap_or(&path).to_string_lossy().replace(std::path::MAIN_SEPARATOR, "/");
let (file_relative, part_index) = split_part_relative(&relative)
.unwrap_or_else(|| panic!("invalid Oliphaunt extension chunk file name {relative}"));
chunks.entry(file_relative).or_default().push((part_index, path));
}
Ok(())
}
fn split_part_relative(relative: &str) -> Option<(String, usize)> {
let (file, index) = relative.rsplit_once(".part")?;
if file.is_empty() || index.len() != 3 || !index.bytes().all(|byte| byte.is_ascii_digit()) {
return None;
}
Some((file.to_owned(), index.parse().ok()?))
}
fn collect_files(root: &Path) -> io::Result<Vec<PathBuf>> {
let mut files = Vec::new();
collect_files_inner(root, &mut files)?;
files.sort();
Ok(files)
}
fn collect_files_inner(path: &Path, files: &mut Vec<PathBuf>) -> io::Result<()> {
if !path.is_dir() {
return Ok(());
}
for entry in fs::read_dir(path)? {
let entry = entry?;
let entry_path = entry.path();
let metadata = fs::metadata(&entry_path)?;
if metadata.is_dir() {
collect_files_inner(&entry_path, files)?;
} else if metadata.is_file() {
files.push(entry_path);
}
}
Ok(())
}
fn sha256_file(path: &Path) -> io::Result<String> {
let mut file = fs::File::open(path)?;
let mut state = [
0x6a09e667_u32,
0xbb67ae85,
0x3c6ef372,
0xa54ff53a,
0x510e527f,
0x9b05688c,
0x1f83d9ab,
0x5be0cd19,
];
let mut pending = [0_u8; 64];
let mut pending_len = 0_usize;
let mut total_len = 0_u64;
let mut input = [0_u8; 64 * 1024];
loop {
let read = file.read(&mut input)?;
if read == 0 {
break;
}
total_len = total_len.wrapping_add(read as u64);
let mut offset = 0_usize;
if pending_len != 0 {
let copied = (64 - pending_len).min(read);
pending[pending_len..pending_len + copied].copy_from_slice(&input[..copied]);
pending_len += copied;
offset += copied;
if pending_len == 64 {
sha256_compress(&mut state, &pending);
pending_len = 0;
}
}
while offset + 64 <= read {
sha256_compress(&mut state, &input[offset..offset + 64]);
offset += 64;
}
if offset != read {
pending[..read - offset].copy_from_slice(&input[offset..read]);
pending_len = read - offset;
}
}
pending[pending_len] = 0x80;
pending_len += 1;
if pending_len > 56 {
pending[pending_len..].fill(0);
sha256_compress(&mut state, &pending);
pending.fill(0);
} else {
pending[pending_len..56].fill(0);
}
pending[56..].copy_from_slice(&total_len.wrapping_mul(8).to_be_bytes());
sha256_compress(&mut state, &pending);
let mut output = String::with_capacity(64);
for word in state {
use std::fmt::Write as _;
write!(&mut output, "{word:08x}").expect("write SHA-256 hex digest");
}
Ok(output)
}
fn sha256_compress(state: &mut [u32; 8], block: &[u8]) {
const K: [u32; 64] = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
];
let mut schedule = [0_u32; 64];
for (index, bytes) in block.chunks_exact(4).take(16).enumerate() {
schedule[index] = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
}
for index in 16..64 {
let s0 = schedule[index - 15].rotate_right(7)
^ schedule[index - 15].rotate_right(18)
^ (schedule[index - 15] >> 3);
let s1 = schedule[index - 2].rotate_right(17)
^ schedule[index - 2].rotate_right(19)
^ (schedule[index - 2] >> 10);
schedule[index] = schedule[index - 16]
.wrapping_add(s0)
.wrapping_add(schedule[index - 7])
.wrapping_add(s1);
}
let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state;
for index in 0..64 {
let sum1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
let choose = (e & f) ^ ((!e) & g);
let temporary1 = h
.wrapping_add(sum1)
.wrapping_add(choose)
.wrapping_add(K[index])
.wrapping_add(schedule[index]);
let sum0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
let majority = (a & b) ^ (a & c) ^ (b & c);
let temporary2 = sum0.wrapping_add(majority);
h = g;
g = f;
f = e;
e = d.wrapping_add(temporary1);
d = c;
c = b;
b = a;
a = temporary1.wrapping_add(temporary2);
}
state[0] = state[0].wrapping_add(a);
state[1] = state[1].wrapping_add(b);
state[2] = state[2].wrapping_add(c);
state[3] = state[3].wrapping_add(d);
state[4] = state[4].wrapping_add(e);
state[5] = state[5].wrapping_add(f);
state[6] = state[6].wrapping_add(g);
state[7] = state[7].wrapping_add(h);
}