use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use tempfile::tempdir;
use walkdir::WalkDir;
use crate::{
action::{ActionError, Context},
action_impl::{rust_toolchain_versions, spawn, spawn_in, ActionImpl},
util::{copy_file, mkdir, UtilError},
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoFetch;
impl ActionImpl for CargoFetch {
fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
rust_toolchain_versions(context)?;
let tmp = tempdir().map_err(CargoError::TempDir)?;
let dest = tmp.path();
copy_partial_tree(context.source_dir(), dest, |path| {
if let Some(name) = path.file_name().map(|s| s.as_encoded_bytes()) {
name == b"Cargo.toml"
|| name == b"Cargo.lock"
|| (name.ends_with(b".rs") && name != b"build.rs")
} else {
false
}
})?;
let lockfile = dest.join("Cargo.lock");
let deny1 = dest.join("deny.toml");
let deny2 = dest.join(".cargo/deny.toml");
let deny = deny1.exists() || deny2.exists();
if lockfile.exists() {
spawn_in(context, &["cargo", "fetch", "--locked"], dest.to_path_buf())?;
if deny {
spawn_in(
context,
&["cargo", "deny", "--locked", "fetch"],
dest.to_path_buf(),
)?;
}
} else {
spawn_in(context, &["cargo", "fetch"], dest.to_path_buf())?;
if deny {
spawn_in(
context,
&["cargo", "deny", "--locked", "fetch"],
dest.to_path_buf(),
)?;
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoFmt;
impl ActionImpl for CargoFmt {
fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
rust_toolchain_versions(context)?;
spawn(context, &["cargo", "fmt", "--check"])?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoClippy;
impl ActionImpl for CargoClippy {
fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
rust_toolchain_versions(context)?;
spawn(
context,
&[
"cargo",
"clippy",
"--offline",
"--locked",
"--workspace",
"--all-targets",
"--no-deps",
"--",
"--deny",
"warnings",
],
)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoDeny;
impl ActionImpl for CargoDeny {
fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
rust_toolchain_versions(context)?;
spawn(
context,
&[
"cargo",
"deny",
"--offline",
"--locked",
"--workspace",
"check",
],
)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoDoc;
impl ActionImpl for CargoDoc {
fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
rust_toolchain_versions(context)?;
spawn(
context,
&[
"env",
"RUSTDOCFLAGS=-D warnings",
"cargo",
"doc",
"--workspace",
],
)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoBuild;
impl ActionImpl for CargoBuild {
fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
rust_toolchain_versions(context)?;
spawn(
context,
&[
"cargo",
"build",
"--offline",
"--locked",
"--workspace",
"--all-targets",
],
)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoTest;
impl ActionImpl for CargoTest {
fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
rust_toolchain_versions(context)?;
spawn(
context,
&["cargo", "test", "--offline", "--locked", "--workspace"],
)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoInstall;
impl ActionImpl for CargoInstall {
fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
rust_toolchain_versions(context)?;
let artifacts = context.artifacts_dir().to_string_lossy().to_string();
spawn(
context,
&[
"cargo",
"install",
"--offline",
"--locked",
"--bins",
"--path=.",
"--root",
&artifacts,
],
)?;
Ok(())
}
}
fn copy_partial_tree<P, PP, F>(src: P, dest: PP, wanted: F) -> Result<(), CargoError>
where
P: AsRef<Path>,
PP: AsRef<Path>,
F: Fn(&Path) -> bool,
{
let src = src.as_ref();
let dest = dest.as_ref();
mkdir(dest)?;
for e in WalkDir::new(src) {
let path = e
.map_err(|err| CargoError::CopyTreeWalkDir(src.into(), err))?
.path()
.to_path_buf();
if wanted(&path) {
let dest = dest.join(path.strip_prefix(src).unwrap_or(&path));
if let Some(parent) = dest.parent() {
if !parent.exists() {
mkdir(parent)?;
}
}
copy_file(&path, &dest)?;
}
}
Ok(())
}
#[derive(Debug, thiserror::Error)]
pub enum CargoError {
#[error(transparent)]
Util(#[from] UtilError),
#[error("failed to list contents of upload directory")]
WalkDir(#[source] walkdir::Error),
#[error("no *.changes file built for deb project")]
NoChanges,
#[error("more than one *.changes file built for deb project")]
ManyChanges,
#[error("failed to list files in directory {0} when copying files")]
CopyTreeWalkDir(PathBuf, #[source] walkdir::Error),
#[error("failed to create a temporary directory")]
TempDir(#[source] std::io::Error),
}
impl From<CargoError> for ActionError {
fn from(value: CargoError) -> Self {
Self::Cargo(value)
}
}