use crate::PyProjectToml;
use anyhow::{Context, Result, bail};
use fs_err as fs;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct UnpackedSdist {
pub tmpdir: tempfile::TempDir,
pub cargo_toml: PathBuf,
pub pyproject_toml: PathBuf,
}
pub fn unpack_sdist(sdist_path: &Path) -> Result<UnpackedSdist> {
let tmp = tempfile::tempdir().context("Failed to create temporary directory")?;
let gz = flate2::read::GzDecoder::new(
fs::File::open(sdist_path)
.with_context(|| format!("Failed to open sdist {}", sdist_path.display()))?,
);
let mut archive = tar::Archive::new(gz);
archive
.unpack(tmp.path())
.context("Failed to unpack source distribution")?;
let entries: Vec<_> = fs::read_dir(tmp.path())
.context("Failed to read unpacked sdist directory")?
.filter_map(|e| e.ok())
.filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
.collect();
let top_dir = match entries.len() {
1 => dunce::canonicalize(entries[0].path()).unwrap_or_else(|_| entries[0].path()),
n => bail!(
"Expected exactly one top-level directory in sdist, found {}",
n
),
};
let pyproject_file = top_dir.join("pyproject.toml");
let cargo_toml = if pyproject_file.is_file() {
let pyproject = PyProjectToml::new(&pyproject_file)?;
if let Some(manifest_path) = pyproject.manifest_path() {
top_dir.join(manifest_path)
} else {
top_dir.join("Cargo.toml")
}
} else {
top_dir.join("Cargo.toml")
};
if !cargo_toml.exists() {
bail!(
"Cargo.toml not found in unpacked sdist at {}",
cargo_toml.display()
);
}
Ok(UnpackedSdist {
tmpdir: tmp,
cargo_toml,
pyproject_toml: pyproject_file,
})
}