use super::PackageArtifact;
use crate::core::config::ResolvedCrateConfig;
use crate::publish::platform::RustTarget;
use anyhow::Result;
use std::fs;
use std::io::Read;
use std::path::Path;
pub fn package_go_ffi(
config: &ResolvedCrateConfig,
target: &RustTarget,
workspace_root: &Path,
output_dir: &Path,
version: &str,
) -> Result<PackageArtifact> {
let lib_name = config.ffi_lib_name();
let header_name = config.ffi_header_name();
let crate_name = &config.name;
let platform = target.platform_for(crate::core::config::extras::Language::Go);
let pkg_name = format!("{crate_name}-go-v{version}-{platform}");
let staging = output_dir.join(&pkg_name);
if staging.exists() {
fs::remove_dir_all(&staging)?;
}
let lib_dir = staging.join("lib");
let include_dir = staging.join("include");
fs::create_dir_all(&lib_dir)?;
fs::create_dir_all(&include_dir)?;
let shared_lib = target.shared_lib_name(&lib_name);
let shared_src = super::find_built_artifact(workspace_root, target, &shared_lib, super::BuildProfile::Release)?;
let shared_dst = lib_dir.join(&shared_lib);
fs::copy(&shared_src, &shared_dst)?;
super::util::fix_macos_dylib_id(target, &shared_dst, &shared_lib)?;
let static_lib = target.static_lib_name(&lib_name);
let static_result = super::find_built_artifact(workspace_root, target, &static_lib, super::BuildProfile::Release);
if let Ok(static_src) = static_result {
fs::copy(&static_src, lib_dir.join(&static_lib))?;
}
let ffi_crate_dir = crate::publish::ffi_stage::find_ffi_crate_dir_pub(config, workspace_root);
let header_src = ffi_crate_dir.join("include").join(&header_name);
if header_src.exists() {
fs::copy(&header_src, include_dir.join(&header_name))?;
}
let archive_name = format!("{pkg_name}.tar.gz");
let archive_path = output_dir.join(&archive_name);
super::create_tar_gz(&staging, &archive_path)?;
let _ = fs::remove_dir_all(&staging);
let checksum = sha256_file(&archive_path)?;
let sidecar_path = output_dir.join(format!("{archive_name}.sha256"));
fs::write(&sidecar_path, format!("{checksum} {archive_name}\n"))?;
Ok(PackageArtifact {
path: archive_path,
name: archive_name,
checksum: Some(checksum),
})
}
fn sha256_file(path: &Path) -> Result<String> {
use anyhow::Context as _;
use sha2::{Digest, Sha256};
let mut file = fs::File::open(path).with_context(|| format!("opening {}", path.display()))?;
let mut hasher = Sha256::new();
let mut buf = [0u8; 65536];
loop {
let n = file.read(&mut buf)?;
if n == 0 {
break;
}
hasher.update(&buf[..n]);
}
let digest = hasher.finalize();
let mut hex = String::with_capacity(digest.len() * 2);
for byte in digest.iter() {
use std::fmt::Write as _;
write!(&mut hex, "{byte:02x}").expect("writing to String never fails");
}
Ok(hex)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::publish::platform::RustTarget;
use tempfile::TempDir;
fn make_config(name: &str) -> crate::core::config::ResolvedCrateConfig {
let cfg: crate::core::config::NewAlefConfig = toml::from_str(&format!(
r#"
[workspace]
languages = ["go"]
[[crates]]
name = "{name}"
sources = ["src/lib.rs"]
"#
))
.unwrap();
cfg.resolve().unwrap().remove(0)
}
#[test]
fn package_go_ffi_writes_checksum_and_sidecar() {
let tmp = TempDir::new().unwrap();
let workspace = tmp.path().join("workspace");
let output_dir = tmp.path().join("dist");
fs::create_dir_all(&output_dir).unwrap();
let target = RustTarget::parse("x86_64-unknown-linux-gnu").unwrap();
let release_dir = workspace.join("target/x86_64-unknown-linux-gnu/release");
fs::create_dir_all(&release_dir).unwrap();
fs::write(release_dir.join("libdemo_go_ffi.so"), b"ELF fake so content").unwrap();
let config = make_config("demo_go");
let artifact = package_go_ffi(&config, &target, &workspace, &output_dir, "1.2.3").unwrap();
assert!(artifact.path.exists(), "archive file must exist");
assert_eq!(
artifact.name, "demo_go-go-v1.2.3-linux-x86_64.tar.gz",
"archive name must embed the version, matching the asset name the setup template requests"
);
let expected_checksum = sha256_file(&artifact.path).unwrap();
assert_eq!(
artifact.checksum.as_deref(),
Some(expected_checksum.as_str()),
"PackageArtifact.checksum must be the archive's real SHA-256 digest"
);
let sidecar_path = output_dir.join(format!("{}.sha256", artifact.name));
assert!(
sidecar_path.exists(),
"SHA-256 sidecar must be written next to the archive"
);
let sidecar_contents = fs::read_to_string(&sidecar_path).unwrap();
assert_eq!(
sidecar_contents,
format!("{expected_checksum} {}\n", artifact.name),
"sidecar contents must be '<digest> <archive name>\\n'"
);
}
}