use std::cell::RefCell;
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use std::rc::Rc;
use cargo_platform::Platform;
use tempfile::{tempdir, TempDir};
use toml_edit::{value, DocumentMut};
pub(crate) type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
#[derive(Debug)]
pub(crate) struct SimpleManifest {
pub(crate) name: String,
pub(crate) version: String,
pub(crate) license: Option<spdx::Expression>,
pub(crate) license_file: Option<(PathBuf, String)>,
pub(crate) authors: Vec<String>,
pub(crate) repository: Option<String>,
pub(crate) dependencies: RefCell<Vec<(Option<Platform>, Rc<TestPackage>)>>,
pub(crate) dev_dependencies: RefCell<Vec<(Option<Platform>, Rc<TestPackage>)>>,
pub(crate) build_dependencies: RefCell<Vec<(Option<Platform>, Rc<TestPackage>)>>,
}
#[derive(Debug)]
pub(crate) struct TestPackage {
pub(crate) dir: TempDir,
pub(crate) manifest: SimpleManifest,
}
fn set_dependency(
doc: &mut DocumentMut,
kind: &str,
dependency: &(Option<Platform>, Rc<TestPackage>),
) {
let (platform, dependency) = dependency;
let val = value(dependency.dir.path().to_str().unwrap());
if let Some(platform) = platform {
let target_spec = match platform {
Platform::Name(name) => name.as_str(),
Platform::Cfg(cfg) => &format!("cfg({cfg})"),
};
doc["target"][target_spec][kind][&dependency.manifest.name]["path"] = val;
} else {
doc[kind][&dependency.manifest.name]["path"] = val;
}
}
impl TestPackage {
pub(crate) fn new(manifest: SimpleManifest) -> Result<Self> {
let pkg = Self {
dir: tempdir()?,
manifest,
};
pkg.update_files()?;
Ok(pkg)
}
pub(crate) fn update_files(&self) -> Result<()> {
let mut doc = DocumentMut::new();
doc["package"]["name"] = value(&self.manifest.name);
doc["package"]["version"] = value(&self.manifest.version);
if let Some(license) = &self.manifest.license {
doc["package"]["license"] = value(license.to_string());
}
if let Some((path, contents)) = &self.manifest.license_file {
let mut license_file = File::create(self.dir.path().join(path))?;
write!(license_file, "{}", contents)?;
doc["package"]["license-file"] = value(path.to_str().unwrap());
}
doc["package"]["authors"] =
value(toml_edit::Array::from_iter(self.manifest.authors.iter()));
if let Some(repository) = &self.manifest.repository {
doc["package"]["repository"] = value(repository);
}
for dependency in self.manifest.dependencies.borrow().iter() {
set_dependency(&mut doc, "dependencies", dependency);
}
for dev_dependency in self.manifest.dev_dependencies.borrow().iter() {
set_dependency(&mut doc, "dev-dependencies", dev_dependency);
}
for build_dependency in self.manifest.build_dependencies.borrow().iter() {
set_dependency(&mut doc, "build-dependencies", build_dependency);
}
write!(File::create(self.dir.path().join("Cargo.toml"))?, "{}", doc)?;
create_dir_all(self.dir.path().join("src"))?;
File::create(self.dir.path().join("src").join("lib.rs"))?;
Command::new("cargo")
.args(["generate-lockfile", "--quiet"])
.current_dir(self.dir.path())
.spawn()?;
Ok(())
}
pub(crate) fn manifest_path(&self) -> PathBuf {
self.dir.path().join("Cargo.toml")
}
}