use flate2::{Compression, write::GzEncoder};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
fn expected_lib_name() -> &'static str {
if cfg!(target_env = "msvc") {
"box2d.lib"
} else {
"libbox2d.a"
}
}
fn default_target_triple() -> String {
if let Ok(t) = env::var("TARGET") {
return t;
}
if let Ok(t) = env::var("CARGO_CFG_TARGET_TRIPLE") {
return t;
}
let arch = std::env::consts::ARCH;
let os = std::env::consts::OS;
match os {
"windows" => format!("{}-pc-windows-msvc", arch),
"macos" => format!("{}-apple-darwin", arch),
"linux" => format!("{}-unknown-linux-gnu", arch),
_ => format!("{}-unknown-{}", arch, os),
}
}
fn compose_archive_name(
crate_short: &str,
version: &str,
target: &str,
link_type: &str,
extra: Option<&str>,
crt: &str,
) -> String {
let extra = extra.unwrap_or("");
if crt.is_empty() {
if extra.is_empty() {
format!(
"{}-prebuilt-{}-{}-{}.tar.gz",
crate_short, version, target, link_type
)
} else {
format!(
"{}-prebuilt-{}-{}-{}{}.tar.gz",
crate_short, version, target, link_type, extra
)
}
} else if extra.is_empty() {
format!(
"{}-prebuilt-{}-{}-{}-{}.tar.gz",
crate_short, version, target, link_type, crt
)
} else {
format!(
"{}-prebuilt-{}-{}-{}{}-{}.tar.gz",
crate_short, version, target, link_type, extra, crt
)
}
}
fn compose_manifest_bytes(
crate_short: &str,
version: &str,
target: &str,
link_type: &str,
crt: &str,
features: Option<&str>,
) -> Vec<u8> {
let mut buf = Vec::new();
use std::io::Write;
let _ = writeln!(
&mut buf,
"{} prebuilt\nversion={}\ntarget={}\nlink={}\ncrt={}",
crate_short, version, target, link_type, crt
);
if let Some(f) = features
&& !f.is_empty()
{
let _ = writeln!(&mut buf, "features={}", f);
}
buf
}
fn locate_sys_out_dir(workspace_root: &Path, target: &str) -> Result<PathBuf, String> {
let profile = env::var("PROFILE").unwrap_or_else(|_| "release".into());
let target_dir = env::var("CARGO_TARGET_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| workspace_root.join("target"));
let build_root_target = target_dir.join(target).join(&profile).join("build");
let build_root_host = target_dir.join(&profile).join("build");
let build_root = if build_root_target.exists() {
build_root_target
} else if build_root_host.exists() {
build_root_host
} else {
return Err(format!(
"Build root not found at {} (or {})",
build_root_target.display(),
build_root_host.display()
));
};
let mut candidates: Vec<PathBuf> = match std::fs::read_dir(&build_root) {
Ok(rd) => rd
.filter_map(|e| e.ok())
.filter_map(|e| {
let p = e.path();
let name = p.file_name()?.to_string_lossy().to_string();
if name.starts_with("boxdd-sys-") {
let out = p.join("out");
if out.exists() { Some(out) } else { None }
} else {
None
}
})
.collect(),
Err(_) => Vec::new(),
};
if candidates.is_empty() {
return Err(format!(
"No boxdd-sys build out directories found under {}",
build_root.display()
));
}
candidates.sort_by_key(|p| std::fs::metadata(p).and_then(|m| m.modified()).ok());
Ok(candidates.pop().unwrap())
}
fn append_headers(
tar: &mut tar::Builder<GzEncoder<fs::File>>,
src_dir: &Path,
dst_root: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let mut stack = vec![src_dir.to_path_buf()];
while let Some(dir) = stack.pop() {
for entry in fs::read_dir(&dir)? {
let entry = entry?;
let p = entry.path();
let rel = p.strip_prefix(src_dir).unwrap();
if p.is_dir() {
stack.push(p);
} else if p
.extension()
.and_then(|s| s.to_str())
.map(|s| s.eq_ignore_ascii_case("h"))
.unwrap_or(false)
{
let mut f = fs::File::open(&p)?;
let dst_path = format!("{}/{}", dst_root, rel.display());
tar.append_file(dst_path, &mut f)?;
}
}
}
Ok(())
}
fn append_license_if_exists(
tar: &mut tar::Builder<GzEncoder<fs::File>>,
src: &Path,
dst: &str,
) -> Result<(), Box<dyn std::error::Error>> {
if src.exists() {
let mut f = fs::File::open(src)?;
let mut hdr = tar::Header::new_gnu();
hdr.set_size(f.metadata()?.len());
hdr.set_mode(0o644);
hdr.set_cksum();
tar.append_data(&mut hdr, dst, &mut f)?;
println!("Added license: {} => {}", src.display(), dst);
} else {
eprintln!("WARN: license file missing: {}", src.display());
}
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let workspace_root = manifest_dir.parent().unwrap();
let target = default_target_triple();
let crate_version = env::var("CARGO_PKG_VERSION").unwrap();
let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let target_features = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default();
let crt = if target_os == "windows" && target_env == "msvc" {
if target_features.split(',').any(|f| f == "crt-static") {
"mt"
} else {
"md"
}
} else {
""
};
let link_type = "static";
let features = env::var("BOXDD_SYS_PKG_FEATURES").unwrap_or_default();
let pkg_dir = PathBuf::from(env::var("BOXDD_SYS_PACKAGE_DIR").unwrap_or_else(|_| {
env::var("OUT_DIR")
.unwrap_or_else(|_| workspace_root.join("packages").display().to_string())
}));
fs::create_dir_all(&pkg_dir)?;
let ar_name = compose_archive_name("boxdd", &crate_version, &target, link_type, None, crt);
let out_path = pkg_dir.join(&ar_name);
println!("Packaging to: {}", out_path.display());
let f = fs::File::create(&out_path)?;
let enc = GzEncoder::new(f, Compression::default());
let mut tar = tar::Builder::new(enc);
let include_root = manifest_dir
.join("third-party")
.join("box2d")
.join("include");
if include_root.exists() {
append_headers(&mut tar, &include_root, "include/box2d")?;
println!("Added headers from {}", include_root.display());
} else {
eprintln!("WARN: include dir not found: {}", include_root.display());
}
append_license_if_exists(
&mut tar,
&workspace_root.join("LICENSE-MIT"),
"licenses/PROJECT-LICENSE-MIT",
)?;
append_license_if_exists(
&mut tar,
&workspace_root.join("LICENSE-APACHE"),
"licenses/PROJECT-LICENSE-APACHE",
)?;
let sys_out = locate_sys_out_dir(workspace_root, &target)?;
let lib_path = sys_out.join(expected_lib_name());
if !lib_path.exists() {
return Err(format!("Static library not found at {}", lib_path.display()).into());
}
let mut f = fs::File::open(&lib_path)?;
tar.append_file(format!("lib/{}", expected_lib_name()), &mut f)?;
println!("Added lib: {}", lib_path.display());
let manifest_txt = compose_manifest_bytes(
"boxdd",
&crate_version,
&target,
link_type,
crt,
if features.is_empty() {
None
} else {
Some(&features)
},
);
let mut hdr = tar::Header::new_gnu();
hdr.set_size(manifest_txt.len() as u64);
hdr.set_mode(0o644);
hdr.set_cksum();
tar.append_data(&mut hdr, "manifest.txt", manifest_txt.as_slice())?;
tar.finish()?;
println!("Package created: {}", out_path.display());
Ok(())
}