#![warn(clippy::all, clippy::pedantic, missing_docs, clippy::cargo)]
pub mod bin;
pub mod test;
#[cfg(test)]
mod test_common;
use std::{
io::{self, Read},
process::ChildStderr,
};
pub use camino::{Utf8Path, Utf8PathBuf};
pub use cargo_metadata::{
diagnostic::{Diagnostic, DiagnosticLevel},
ArtifactProfile, CompilerMessage, PackageId, Target,
};
use lazy_static::lazy_static;
use regex::Regex;
use tracing::{debug, info, instrument, warn};
const MSG_FORMAT: &str = "--message-format=json-diagnostic-rendered-ansi";
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct ExecutableArtifact {
pub package_id: PackageId,
pub target: Target,
pub profile: ArtifactProfile,
pub features: Vec<String>,
pub filenames: Vec<Utf8PathBuf>,
pub executable: Utf8PathBuf,
pub fresh: bool,
}
impl ExecutableArtifact {
fn maybe_from(art: cargo_metadata::Artifact) -> Option<Self> {
let cargo_metadata::Artifact {
package_id,
target,
profile,
features,
filenames,
executable,
fresh,
..
} = art;
Some(Self {
package_id,
target,
profile,
features,
filenames,
executable: executable?,
fresh,
})
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum PackageSpec {
Any,
Name(String),
Id(PackageId),
}
impl PackageSpec {
const ANY_REPR: &'static str = "*";
pub fn name(name: impl Into<String>) -> Self {
Self::Name(name.into())
}
#[must_use]
pub fn as_repr(&self) -> &str {
match self {
Self::Any => Self::ANY_REPR,
Self::Name(repr) | Self::Id(PackageId { repr }) => repr,
}
}
#[must_use]
pub fn into_repr(self) -> String {
match self {
Self::Any => Self::ANY_REPR.to_owned(),
Self::Name(repr) | Self::Id(PackageId { repr }) => repr,
}
}
}
impl From<PackageId> for PackageSpec {
fn from(id: PackageId) -> Self {
Self::Id(id)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct FeatureSpec(FeatureSpecInner);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum FeatureSpecInner {
Subset {
include_default: bool,
features: Vec<String>,
},
All,
}
impl FeatureSpec {
#[must_use]
pub fn new(features: Vec<String>) -> Self {
Self::new_with(true, features)
}
#[must_use]
pub fn new_no_default(features: Vec<String>) -> Self {
Self::new_with(false, features)
}
fn new_with(include_default: bool, features: Vec<String>) -> Self {
Self(FeatureSpecInner::Subset {
include_default,
features,
})
}
#[must_use]
pub fn all() -> Self {
Self(FeatureSpecInner::All)
}
#[must_use]
pub fn default_only() -> Self {
Self::new(Vec::new())
}
#[must_use]
pub fn none() -> Self {
Self::new_no_default(Vec::new())
}
pub fn feature(&mut self, feature: String) -> &mut Self {
match &mut self.0 {
FeatureSpecInner::Subset { features, .. } => {
features.push(feature);
}
FeatureSpecInner::All => {
info!("Ignoring feature append as set to all")
}
}
self
}
fn to_args(&self) -> Vec<String> {
match &self.0 {
FeatureSpecInner::All => vec!["--all-features".into()],
FeatureSpecInner::Subset {
include_default,
features,
} => {
let mut args = Vec::new();
if !features.is_empty() {
args.push("--features".into());
args.push(features.join(","));
}
if !include_default {
args.push("--no-default-features".into());
}
args
}
}
}
}
pub(crate) fn handle_compiler_msg(
msg: CompilerMessage,
cb: &mut Option<Box<dyn FnMut(CompilerMessage)>>,
) {
debug!(?msg, "Got compiler message");
if let Some(cb) = cb {
cb(msg)
}
}
#[derive(Debug, thiserror::Error, displaydoc::Display)]
pub enum BuildError {
RunCargo(#[from] io::Error),
NotFound(String),
PackageNotFound(String),
Cargo(String),
}
impl BuildError {
#[instrument]
fn from_stderr(mut stderr: ChildStderr) -> Self {
let mut stderr_buf = String::new();
if let Err(err) = stderr.read_to_string(&mut stderr_buf) {
return Self::RunCargo(err);
}
lazy_static! {
static ref NOT_FOUND_RE: Regex =
Regex::new(r"error: no \w+ target named `(?P<n>.*?)`").unwrap();
static ref PKG_NOT_FOUND_RE: Regex = Regex::new(
r"error: package ID specification `(?P<p>.*?)` did not match any packages"
)
.unwrap();
}
#[allow(clippy::option_if_let_else)]
if let Some(caps) = NOT_FOUND_RE.captures(&stderr_buf) {
let name = caps.name("n").unwrap().as_str().to_owned();
BuildError::NotFound(name)
} else if let Some(caps) = PKG_NOT_FOUND_RE.captures(&stderr_buf) {
let name = caps.name("p").unwrap().as_str().to_owned();
BuildError::PackageNotFound(name)
} else {
BuildError::Cargo(stderr_buf)
}
}
}